1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
|
---
title: 使用 XMLHttpRequest
slug: Web/API/XMLHttpRequest/Using_XMLHttpRequest
tags:
- AJAX
- AJAXfile
- DOM
- HTTP
- JXON
- XHR
- XML
- XMLHttpRequest
translation_of: Web/API/XMLHttpRequest/Using_XMLHttpRequest
---
<div> {{APIRef("XMLHttpRequest")}}</div>
<p>在该教程中,我们将使用<span class="seoSummary">{{domxref("XMLHttpRequest")}} 来发送 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP">HTTP</a> 请求以实现网站和服务器之间的数据交换。</span><code>XMLHttpRequest</code><span class="seoSummary">常见和晦涩的使用情况都将包含在例子中。</span></p>
<p>发送一个 HTTP 请求,需要创建一个 <code>XMLHttpRequest</code> 对象,打开一个 URL,最后发送请求。当所有这些事务完成后,该对象将会包含一些诸如响应主体或 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">HTTP status</a> 的有用信息。</p>
<pre class="brush: js notranslate">function reqListener () {
console.log(this.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send();</pre>
<h2 id="请求类型">请求类型</h2>
<p>通过 <code>XMLHttpRequest</code> 生成的请求可以有两种方式来获取数据,异步模式或同步模式。请求的类型是由这个 <code>XMLHttpRequest</code> 对象的 <a href="/zh-CN/docs/Web/API/XMLHttpRequest/open">open()</a> 方法的第三个参数<code>async</code>的值决定的。如果该参数的值为 <code>false</code>,则该 <code>XMLHttpRequest</code>请求以同步模式进行,否则该过程将以异步模式完成。这两种类型请求的详细讨论和指南可以在<a href="/zh-CN/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests">同步和异步请求</a>页找到。</p>
<div class="note"><strong>注意:</strong>由于对用户体验的负面影响,从 Gecko 30.0{{geckoRelease("30.0")}}版本开始,在主线程上的同步请求已经被弃用。</div>
<div class="note"><strong>注意:</strong><code>XMLHttpRequest</code> 构造函数并不仅限于 XML 文档。它之所以使用“XML”开头是因为在它诞生之时,原先用于异步数据交换的主要格式便是XML。</div>
<h2 id="处理响应">处理响应</h2>
<p>W3C规范定义了 {{domxref("XMLHttpRequest.XMLHttpRequest", "XMLHttpRequest()")}} 对象的几种类型的<a class="external" href="https://xhr.spec.whatwg.org/">响应属性</a>。这些属性告诉客户端关于 <code>XMLHttpRequest</code> 返回状态的重要信息。一些处理非文本返回类型的用例,可能包含下面章节所描述的一些操作和分析。</p>
<h3 id="分析并操作_responseXML属性">分析并操作 responseXML属性</h3>
<p>如果你使用 <code>XMLHttpRequest</code> 来获得一个远程的 XML 文档的内容,{{domxref("XMLHttpRequest.responseXML", "responseXML")}} 属性将会是一个由 XML 文档解析而来的 DOM 对象,这很难被操作和分析。这里有五种主要的分析 XML 文档的方式:</p>
<ol>
<li>使用 <a href="/zh-CN/docs/Web/XPath">XPath</a> 定位到文档的指定部分。</li>
<li>手动 <a href="/zh-CN/docs/Web/Guide/Parsing_and_serializing_XML">解析和序列化 XML</a> 为字符串或对象。</li>
<li>使用 <a href="/zh-CN/docs/XMLSerializer">XMLSerializer</a> 把 DOM 树序列化成字符串或文件。</li>
<li>如果你预先知道 XML 文档的内容,你可以使用 <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp">RegExp</a>。如果你用 <code>RegExp</code> 扫描时受到换行符的影响,你也许想要删除所有的换行符。然而,这种方法是"最后手段",因为如果 XML 代码发生轻微变化,该方法将可能失败。</li>
</ol>
<div class="note"><strong>注意:</strong> 在 W3C <a class="external" href="http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html">XMLHttpRequest</a> 规范中允许 HTML 通过 XMLHttpRequest.responseXML 属性进行解析。更多详细内容请阅读 <a href="/zh-CN/docs/Web/API/XMLHttpRequest/HTML_in_XMLHttpRequest">HTML in XMLHttpRequest</a> 。本条注意已在英文原文中更新。</div>
<div class="note">
<p><strong>注意:</strong> <code>XMLHttpRequest</code> 现在可以使用 {{domxref("XMLHttpRequest.responseXML", "responseXML")}} 属性解释 HTML。请阅读 <a href="/en-US/docs/Web/API/XMLHttpRequest/HTML_in_XMLHttpRequest">HTML in XMLHttpRequest</a> 这篇文章了解相关用法。</p>
</div>
<h3 id="解析和操作包含_HTML_文档的_responseText_属性">解析和操作包含 HTML 文档的 responseText 属性</h3>
<p>如果使用 <code>XMLHttpRequest</code> 从远端获取一个 HTML 页面,则所有 HTML 标记会以字符串的形式存放在responseText 属性里,这样就使得操作和解析这些标记变得困难。解析这些HTML标记主要有三种方式:</p>
<ol>
<li>使用 <code>XMLHttpRequest.responseXML</code> 属性。</li>
<li>将内容通过 <code>fragment.body.innerHTML</code> 注入到一个 <a href="/zh-CN/docs/Web/API/DocumentFragment">文档片段</a> 中,并遍历 DOM 中的片段。</li>
<li>如果你预先知道 HTML 文档的内容,你可以使用 <a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp">RegExp </a>。如果你用 RegExp 扫描时受到换行符的影响,你也许想要删除所有的换行符。 然而,这种方法是"最后手段",因为如果 HTML 代码发生轻微变化,该方法将可能失败。</li>
</ol>
<h2 id="处理二进制数据">处理二进制数据</h2>
<p>尽管 {{domxref("XMLHttpRequest")}} 一般用来发送和接收文本数据,但其实也可以发送和接收二进制内容。有许多经过良好测试的方法来强制使用 <code>XMLHttpRequest</code> <span style="line-height: 1.5;">发送二进制数据。利用 </span> <code>XMLHttpRequest</code> 对象<span style="line-height: 1.5;">的 <span style="line-height: 1.5;"> </span></span> {{domxref("XMLHttpRequest.overrideMimeType", "overrideMimeType()")}} <span style="line-height: 1.5;"> 方法是一个解决方案,虽然它并不是一个标准方法。</span></p>
<pre class="brush:js notranslate">var oReq = new XMLHttpRequest();
oReq.open("GET", url);
// 以二进制字符串形式检索未处理的数据
oReq.overrideMimeType("text/plain; charset=x-user-defined");
/* ... */
</pre>
<p>然而,自从 {{domxref("XMLHttpRequest.responseType", "responseType")}} 属性目前支持大量附加的内容类型后,已经出现了很多的现代技术,它们使得发送和接收二进制数据变得更加容易。</p>
<p>例如,考虑以下代码,它使用 <code>"arraybuffer"</code> 的 <code>responseType</code> 来将远程内容获取到一个存储原生二进制数据的 {{jsxref("ArrayBuffer")}} 对象中。</p>
<pre class="brush:js notranslate">var oReq = new XMLHttpRequest();
oReq.onload = function(e) {
var arraybuffer = oReq.response; // 不是 responseText !
/* ... */
}
oReq.open("GET", url);
oReq.responseType = "arraybuffer";
oReq.send();</pre>
<p>更多示例请参考 <a href="/zh-CN/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data">发送和接收二进制数据</a>。</p>
<h2 id="监测进度">监测进度</h2>
<p><code>XMLHttpRequest</code> 提供了各种在请求被处理期间发生的事件以供监听。这包括定期进度通知、 错误通知,等等。</p>
<p>支持 DOM 的 progress 事件监测之于 <code>XMLHttpRequest</code> 传输,遵循 Web API <a class="external" href="http://dev.w3.org/2006/webapi/progress/Progress.html">进度事件规范</a>:这些事件实现了 {{domxref("ProgressEvent")}} 接口。</p>
<dl>
<dt>{{event("progress")}}</dt>
<dd>检索的数据量发生了变化。</dd>
<dt>{{event("load")}}</dt>
<dd>传输完成,所有数据保存在 <code>response</code> 中。</dd>
</dl>
<pre class="brush:js notranslate">var oReq = new XMLHttpRequest();
oReq.addEventListener("progress", updateProgress);
oReq.addEventListener("load" , transferComplete);
oReq.addEventListener("error", transferFailed );
oReq.addEventListener("abort", transferCanceled);
oReq.open();
// ...
// 服务端到客户端的传输进程(下载)
function updateProgress (oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total * 100;
// ...
} else {
// 总大小未知时不能计算进程信息
}
}
function transferComplete(evt) {
console.log("The transfer is complete.");
}
function transferFailed(evt) {
console.log("An error occurred while transferring the file.");
}
function transferCanceled(evt) {
console.log("The transfer has been canceled by the user.");
}</pre>
<p>第 3-6 行为多种事件添加了事件监听,这些事件在使用 <code>XMLHttpRequest</code> 执行数据传输时被发出。</p>
<div class="note"><strong>注意:</strong> 你需要在请求调用 <code>open()</code> 之前添加事件监听。否则 <code>progress</code> 事件将不会被触发。</div>
<p>在上一个例子中,progress 事件被指定由 <code>updateProgress()</code> 函数处理,并接收到传输的总字节数和已经传输的字节数,它们分别在事件对象的 <code>total</code> 和 <code>loaded</code> 属性里。但是如果 <code>lengthComputable</code> 属性的值是 false,那么意味着总字节数是未知并且 total 的值为零。</p>
<p>progress 事件同时存在于下载和上传的传输。下载相关事件在 <code>XMLHttpRequest</code> 对象上被触发,就像上面的例子一样。上传相关事件在 <code>XMLHttpRequest.upload</code> 对象上被触发,像下面这样:</p>
<pre class="brush:js notranslate">var oReq = new XMLHttpRequest();
oReq.upload.addEventListener("progress", updateProgress);
oReq.upload.addEventListener("load" , transferComplete);
oReq.upload.addEventListener("error", transferFailed );
oReq.upload.addEventListener("abort", transferCanceled);
oReq.open();
</pre>
<div class="note"><strong>注意:</strong>progress 事件在使用 <code>file:</code> 协议的情况下是无效的。</div>
<div class="note"><strong>注意:</strong>从 {{Gecko("9.0")}} 开始,进度事件现在可以依托于每一个传入的数据块,包括进度事件被触发前在已经接受了最后一个数据包且连接已经被关闭的情况下接收到的最后一个块。这种情况下,当该数据包的 load 事件发生时 progress 事件会被自动触发。这使你可以只关注 progress 事件就可以可靠的监测进度。</div>
<div class="note"><strong>注意:</strong>在 {{Gecko("12.0")}} 中,当 <code>responseType</code> 为 "moz-blob" 时,如果你的 progress 事件被触发,则响应的值是一个包含了接收到的数据的 {{domxref("Blob")}} 。</div>
<p>使用 <code>loadend</code> 事件可以侦测到所有的三种加载结束条件(<code>abort</code>、<code>load</code>,或 <code>error</code>):</p>
<pre class="brush:js notranslate">req.addEventListener("loadend", loadEnd);
function loadEnd(e) {
console.log("The transfer finished (although we don't know if it succeeded or not).");
}
</pre>
<p>需要注意的是,没有方法可以确切的知道 <code>loadend</code> 事件接收到的信息是来自何种条件引起的操作终止;但是你可以在所有传输结束的时候使用这个事件处理。</p>
<h2 id="提交表单和上传文件">提交表单和上传文件</h2>
<p><code>XMLHttpRequest</code> 的实例有两种方式提交表单:</p>
<ul>
<li>使用 AJAX</li>
<li>使用 {{domxref("XMLHttpRequest.FormData", "FormData")}} API</li>
</ul>
<p>第二种方式(使用 <code>FormData</code> API)是最简单最快捷的,但是缺点是被收集的数据无法使用<a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify"> JSON.stringify()</a> 转换为一个 JSON 字符串。<br>
只使用 AJAX 则更为复杂,但也更灵活、更强大。</p>
<h3 id="仅使用_XMLHttpRequest">仅使用 XMLHttpRequest</h3>
<p>在大多数用例中,提交表单时即便不使用 <code>FormData</code> API 也不会要求其他的 API。唯一的例外情况是,<strong>如果你要上传一个或多个文件</strong>,你需要额外的 <code><a href="/zh-CN/docs/Web/API/FileReader">FileReader</a></code> API。</p>
<h4 id="提交方法简介">提交方法简介</h4>
<p>一个 html {{ HTMLElement("form") }} 可以用以下四种方式发送:</p>
<ul>
<li>使用 <code>POST</code> 方法,并将 <code>enctype</code> 属性设置为 <code>application/x-www-form-urlencoded</code> (默认)</li>
<li>使用 <code>POST</code> 方法,并将 <code>enctype</code> 属性设置为 <code>text/plain</code></li>
<li>使用 <code>POST</code> 方法,并将 <code>enctype</code> 属性设置为 <code>multipart/form-data</code></li>
<li>使用 <code>GET</code> 方法(这种情况下 <code>enctype</code> 属性会被忽略)</li>
</ul>
<p>现在,我们提交一个表单,它里面有两个字段,分别被命名为 <code>foo</code> 和 <code>baz</code>。如果你用 <code>POST</code> 方法,那么服务器将会接收到一个字符串类似于下面三种情况之一,其中的区别依赖于你采用何种编码类型:</p>
<ul>
<li>
<p>方法:<code>POST</code>;编码类型:<code>application/x-www-form-urlencoded</code>(默认):</p>
</li>
</ul>
<pre class="brush:plain notranslate">Content-Type: application/x-www-form-urlencoded
foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A</pre>
<ul>
<li>
<p>方法:<code>POST</code>;编码类型:<code>text/plain</code>:</p>
<pre class="brush:plain notranslate">Content-Type: text/plain
foo=bar
baz=The first line.
The second line.</pre>
</li>
<li>
<p>方法:<code>POST</code>;编码类型:<code><a href="/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types#multipartform-data">multipart/form-data</a></code>:</p>
<pre class="brush:plain notranslate">Content-Type: multipart/form-data; boundary=---------------------------314911788813839
-----------------------------314911788813839
Content-Disposition: form-data; name="foo"
bar
-----------------------------314911788813839
Content-Disposition: form-data; name="baz"
The first line.
The second line.
-----------------------------314911788813839--</pre>
</li>
</ul>
<p>相反的,如果你用 <code>GET</code> 方法,像下面这样的字符串将被简单的附加到 URL:</p>
<pre class="brush:plain notranslate">?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.</pre>
<h4 id="一个小框架">一个小框架</h4>
<p>所有这些事情都是由浏览器在你提交一个 {{ HTMLElement("form") }} 的时候自动完成的。但是如果你想要用 JavaScript 做同样的事情,你不得不告诉解释器所有的事。那么,如何发送表单这件事在使用纯粹的 AJAX 时会复杂到无法在这里解释清楚。基于这个原因,我们提供一个完整的(但仍然教条的)框架,它可以使用所有的四种提交方式,甚至上传文件:</p>
<div style="height: 400px; margin-bottom: 12px; overflow: auto;">
<pre class="brush: html notranslate"><!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Sending forms with pure AJAX &ndash; MDN</title>
<script type="text/javascript">
"use strict";
/*\
|*|
|*| :: XMLHttpRequest.prototype.sendAsBinary() Polyfill ::
|*|
|*| https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#sendAsBinary()
\*/
if (!XMLHttpRequest.prototype.sendAsBinary) {
XMLHttpRequest.prototype.sendAsBinary = function(sData) {
var nBytes = sData.length, ui8Data = new Uint8Array(nBytes);
for (var nIdx = 0; nIdx < nBytes; nIdx++) {
ui8Data[nIdx] = sData.charCodeAt(nIdx) & 0xff;
}
/* send as ArrayBufferView...: */
this.send(ui8Data);
/* ...or as ArrayBuffer (legacy)...: this.send(ui8Data.buffer); */
};
}
/*\
|*|
|*| :: AJAX Form Submit Framework ::
|*|
|*| https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest
|*|
|*| This framework is released under the GNU Public License, version 3 or later.
|*| https://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*| Syntax:
|*|
|*| AJAXSubmit(HTMLFormElement);
\*/
var AJAXSubmit = (function () {
function ajaxSuccess () {
/* console.log("AJAXSubmit - Success!"); */
console.log(this.responseText);
/* you can get the serialized data through the "submittedData" custom property: */
/* console.log(JSON.stringify(this.submittedData)); */
}
function submitData (oData) {
/* the AJAX request... */
var oAjaxReq = new XMLHttpRequest();
oAjaxReq.submittedData = oData;
oAjaxReq.onload = ajaxSuccess;
if (oData.technique === 0) {
/* method is GET */
oAjaxReq.open("get", oData.receiver.replace(/(?:\?.*)?$/,
oData.segments.length > 0 ? "?" + oData.segments.join("&") : ""), true);
oAjaxReq.send(null);
} else {
/* method is POST */
oAjaxReq.open("post", oData.receiver, true);
if (oData.technique === 3) {
/* enctype is multipart/form-data */
var sBoundary = "---------------------------" + Date.now().toString(16);
oAjaxReq.setRequestHeader("Content-Type", "multipart\/form-data; boundary=" + sBoundary);
oAjaxReq.sendAsBinary("--" + sBoundary + "\r\n" +
oData.segments.join("--" + sBoundary + "\r\n") + "--" + sBoundary + "--\r\n");
} else {
/* enctype is application/x-www-form-urlencoded or text/plain */
oAjaxReq.setRequestHeader("Content-Type", oData.contentType);
oAjaxReq.send(oData.segments.join(oData.technique === 2 ? "\r\n" : "&"));
}
}
}
function processStatus (oData) {
if (oData.status > 0) { return; }
/* the form is now totally serialized! do something before sending it to the server... */
/* doSomething(oData); */
/* console.log("AJAXSubmit - The form is now serialized. Submitting..."); */
submitData (oData);
}
function pushSegment (oFREvt) {
this.owner.segments[this.segmentIdx] += oFREvt.target.result + "\r\n";
this.owner.status--;
processStatus(this.owner);
}
function plainEscape (sText) {
/* How should I treat a text/plain form encoding?
What characters are not allowed? this is what I suppose...: */
/* "4\3\7 - Einstein said E=mc2" ----> "4\\3\\7\ -\ Einstein\ said\ E\=mc2" */
return sText.replace(/[\s\=\\]/g, "\\$&");
}
function SubmitRequest (oTarget) {
var nFile, sFieldType, oField, oSegmReq, oFile, bIsPost = oTarget.method.toLowerCase() === "post";
/* console.log("AJAXSubmit - Serializing form..."); */
this.contentType = bIsPost && oTarget.enctype ? oTarget.enctype : "application\/x-www-form-urlencoded";
this.technique = bIsPost ?
this.contentType === "multipart\/form-data" ? 3 : this.contentType === "text\/plain" ? 2 : 1 : 0;
this.receiver = oTarget.action;
this.status = 0;
this.segments = [];
var fFilter = this.technique === 2 ? plainEscape : escape;
for (var nItem = 0; nItem < oTarget.elements.length; nItem++) {
oField = oTarget.elements[nItem];
if (!oField.hasAttribute("name")) { continue; }
sFieldType = oField.nodeName.toUpperCase() === "INPUT" ? oField.getAttribute("type").toUpperCase() : "TEXT";
if (sFieldType === "FILE" && oField.files.length > 0) {
if (this.technique === 3) {
/* enctype is multipart/form-data */
for (nFile = 0; nFile < oField.files.length; nFile++) {
oFile = oField.files[nFile];
oSegmReq = new FileReader();
/* (custom properties:) */
oSegmReq.segmentIdx = this.segments.length;
oSegmReq.owner = this;
/* (end of custom properties) */
oSegmReq.onload = pushSegment;
this.segments.push("Content-Disposition: form-data; name=\"" +
oField.name + "\"; filename=\"" + oFile.name +
"\"\r\nContent-Type: " + oFile.type + "\r\n\r\n");
this.status++;
oSegmReq.readAsBinaryString(oFile);
}
} else {
/* enctype is application/x-www-form-urlencoded or text/plain or
method is GET: files will not be sent! */
for (nFile = 0; nFile < oField.files.length;
this.segments.push(fFilter(oField.name) + "=" + fFilter(oField.files[nFile++].name)));
}
} else if ((sFieldType !== "RADIO" && sFieldType !== "CHECKBOX") || oField.checked) {
/* NOTE: this will submit _all_ submit buttons. Detecting the correct one is non-trivial. */
/* field type is not FILE or is FILE but is empty */
this.segments.push(
this.technique === 3 ? /* enctype is multipart/form-data */
"Content-Disposition: form-data; name=\"" + oField.name + "\"\r\n\r\n" + oField.value + "\r\n"
: /* enctype is application/x-www-form-urlencoded or text/plain or method is GET */
fFilter(oField.name) + "=" + fFilter(oField.value)
);
}
}
processStatus(this);
}
return function (oFormElement) {
if (!oFormElement.action) { return; }
new SubmitRequest(oFormElement);
};
})();
</script>
</head>
<body>
<h1>Sending forms with pure AJAX</h1>
<h2>Using the GET method</h2>
<form action="register.php" method="get" onsubmit="AJAXSubmit(this); return false;">
<fieldset>
<legend>Registration example</legend>
<p>
First name: <input type="text" name="firstname" /><br />
Last name: <input type="text" name="lastname" />
</p>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
</form>
<h2>Using the POST method</h2>
<h3>Enctype: application/x-www-form-urlencoded (default)</h3>
<form action="register.php" method="post" onsubmit="AJAXSubmit(this); return false;">
<fieldset>
<legend>Registration example</legend>
<p>
First name: <input type="text" name="firstname" /><br />
Last name: <input type="text" name="lastname" />
</p>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
</form>
<h3>Enctype: text/plain</h3>
<form action="register.php" method="post" enctype="text/plain"
onsubmit="AJAXSubmit(this); return false;">
<fieldset>
<legend>Registration example</legend>
<p>
Your name: <input type="text" name="user" />
</p>
<p>
Your message:<br />
<textarea name="message" cols="40" rows="8"></textarea>
</p>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
</form>
<h3>Enctype: multipart/form-data</h3>
<form action="register.php" method="post" enctype="multipart/form-data"
onsubmit="AJAXSubmit(this); return false;">
<fieldset>
<legend>Upload example</legend>
<p>
First name: <input type="text" name="firstname" /><br />
Last name: <input type="text" name="lastname" /><br />
Sex:
<input id="sex_male" type="radio" name="sex" value="male" />
<label for="sex_male">Male</label>
<input id="sex_female" type="radio" name="sex" value="female" />
<label for="sex_female">Female</label><br />
Password: <input type="password" name="secret" /><br />
What do you prefer:
<select name="image_type">
<option>Books</option>
<option>Cinema</option>
<option>TV</option>
</select>
</p>
<p>
Post your photos:
<input type="file" multiple name="photos[]">
</p>
<p>
<input id="vehicle_bike" type="checkbox" name="vehicle[]" value="Bike" />
<label for="vehicle_bike">I have a bike</label><br />
<input id="vehicle_car" type="checkbox" name="vehicle[]" value="Car" />
<label for="vehicle_car">I have a car</label>
</p>
<p>
Describe yourself:<br />
<textarea name="description" cols="50" rows="8"></textarea>
</p>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
</form>
</body>
</html></pre>
</div>
<p>要测试它的话,创建一个名为 <code>register.php</code> 的页面(作为示例表单的 <code>action</code> 属性)并且只输入以下内容:</p>
<pre class="brush: php notranslate"><?php
/* register.php */
header("Content-type: text/plain");
/*
NOTE: You should never use `print_r()` in production scripts, or
otherwise output client-submitted data without sanitizing it first.
Failing to sanitize can lead to cross-site scripting vulnerabilities.
*/
echo ":: data received via GET ::\n\n";
print_r($_GET);
echo "\n\n:: Data received via POST ::\n\n";
print_r($_POST);
echo "\n\n:: Data received as \"raw\" (text/plain encoding) ::\n\n";
if (isset($HTTP_RAW_POST_DATA)) { echo $HTTP_RAW_POST_DATA; }
echo "\n\n:: Files received ::\n\n";
print_r($_FILES);</pre>
<p>激活这些代码的语法很简单:</p>
<pre class="syntaxbox notranslate">AJAXSubmit(myForm);</pre>
<div class="note"><strong>注意:</strong> 该框架使用 {{domxref("FileReader")}} API 进行文件的上传。这是一个较新的 API 并且还未在 IE9 及以下版本的浏览器中实现。因此,使用 AJAX 上传仍是一项<strong>实验性的技术</strong>。如果你不需要上传 二进制文件,该框架在大多数浏览器中运行良好。</div>
<div class="note"><strong>注意:</strong> 发送二进制内容的最佳途径是通过 {{jsxref("ArrayBuffer", "ArrayBuffers")}} 或 {{domxref("Blob", "Blobs")}} 结合 {{domxref("XMLHttpRequest.send()", "send()")}} 方法甚至 <code>FileReader</code> API 的 {{domxref("FileReader.readAsArrayBuffer()", "readAsArrayBuffer()")}} 方法。但是,自从该脚本的目的变成处理 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify">可字符串化</a> 的原始数据以来,我们使用 {{domxref("XMLHttpRequest.sendAsBinary()", "sendAsBinary()")}} 方法结合 <code>FileReader</code> API 的 {{domxref("FileReader.readAsBinaryString()", "readAsBinaryString()")}} 方法。同样地,上述脚本仅当你处理小文件时行之有效。如果不打算上传二进制内容,就考虑使用 <code>FormData</code> API 来替代。</div>
<div class="note"><strong>注意:</strong> 非标准的 <code>sendAsBinary</code> 方法从 Gecko 31 {{geckoRelease(31)}} 开始将会废弃并且会很快被移除。标准方法 <code>send(Blob data)</code> 将会取而代之。</div>
<h3 id="使用_FormData_对象">使用 FormData 对象</h3>
<p>{{domxref("XMLHttpRequest.FormData", "FormData")}} 构造函数能使你编译一个键/值对的集合,然后使用 <code>XMLHttpRequest</code> 发送出去。其主要用于发送表格数据,但是也能被单独用来传输表格中用户指定的数据。传输的数据格式与表格使用 <code>submit()</code> 方法发送数据的格式一致,如果该表格的编码类型被设为 "multipart/form-data". FormData 对象可以被结合 <code>XMLHttpRequest</code> 的多种方法利用。例如,想了解如何利用 FormData 与 XMLHttpRequests, 请转到 <a href="/en-US/docs/DOM/XMLHttpRequest/FormData/Using_FormData_Objects">Using FormData Objects</a> 页面。为了说教的目的,这里有一个早期的<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest$edit#A_little_vanilla_framework">例子</a>,被转译成了使用<strong> <code>FormData</code> API </strong>的形式。注意以下代码片段:</p>
<div style="height: 400px; margin-bottom: 12px; overflow: auto;">
<pre class="brush: html notranslate"><!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" charset="UTF-8" />
<title>Sending forms with FormData &ndash; MDN</title>
<script>
"use strict";
function ajaxSuccess () {
console.log(this.responseText);
}
function AJAXSubmit (oFormElement) {
if (!oFormElement.action) { return; }
var oReq = new XMLHttpRequest();
oReq.onload = ajaxSuccess();
if (oFormElement.method.toLowerCase() === "post") {
oReq.open("post", oFormElement.action);
oReq.send(new FormData(oFormElement));
} else {
var oField, sFieldType, nFile, sSearch = "";
for (var nItem = 0; nItem < oFormElement.elements.length; nItem++) {
oField = oFormElement.elements[nItem];
if (!oField.hasAttribute("name")) { continue; }
sFieldType = oField.nodeName.toUpperCase() === "INPUT" ?
oField.getAttribute("type").toUpperCase() : "TEXT";
if (sFieldType === "FILE") {
for (nFile = 0; nFile < oField.files.length;
sSearch += "&" + escape(oField.name) + "=" + escape(oField.files[nFile++].name));
} else if ((sFieldType !== "RADIO" && sFieldType !== "CHECKBOX") || oField.checked) {
sSearch += "&" + escape(oField.name) + "=" + escape(oField.value);
}
}
oReq.open("get", oFormElement.action.replace(/(?:\?.*)?$/, sSearch.replace(/^&/, "?")), true);
oReq.send(null);
}
}
</script>
</head>
<body>
<h1>Sending forms with FormData</h1>
<h2>Using the GET method</h2>
<form action="register.php" method="get" onsubmit="AJAXSubmit(this); return false;">
<fieldset>
<legend>Registration example</legend>
<p>
First name: <input type="text" name="firstname" /><br />
Last name: <input type="text" name="lastname" />
</p>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
</form>
<h2>Using the POST method</h2>
<h3>Enctype: application/x-www-form-urlencoded (default)</h3>
<form action="register.php" method="post" onsubmit="AJAXSubmit(this); return false;">
<fieldset>
<legend>Registration example</legend>
<p>
First name: <input type="text" name="firstname" /><br />
Last name: <input type="text" name="lastname" />
</p>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
</form>
<h3>Enctype: text/plain</h3>
<p>The text/plain encoding is not supported by the FormData API.</p>
<h3>Enctype: multipart/form-data</h3>
<form action="register.php" method="post" enctype="multipart/form-data"
onsubmit="AJAXSubmit(this); return false;">
<fieldset>
<legend>Upload example</legend>
<p>
First name: <input type="text" name="firstname" /><br />
Last name: <input type="text" name="lastname" /><br />
Sex:
<input id="sex_male" type="radio" name="sex" value="male" />
<label for="sex_male">Male</label>
<input id="sex_female" type="radio" name="sex" value="female" />
<label for="sex_female">Female</label><br />
Password: <input type="password" name="secret" /><br />
What do you prefer:
<select name="image_type">
<option>Books</option>
<option>Cinema</option>
<option>TV</option>
</select>
</p>
<p>
Post your photos:
<input type="file" multiple name="photos[]">
</p>
<p>
<input id="vehicle_bike" type="checkbox" name="vehicle[]" value="Bike" />
<label for="vehicle_bike">I have a bike</label><br />
<input id="vehicle_car" type="checkbox" name="vehicle[]" value="Car" />
<label for="vehicle_car">I have a car</label>
</p>
<p>
Describe yourself:<br />
<textarea name="description" cols="50" rows="8"></textarea>
</p>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
</form>
</body>
</html></pre>
</div>
<div class="note"><strong>注意:</strong> 如之前所述,<strong>{{domxref("FormData")}} 对象并不是 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify">可字符串化(stringifiable)</a> 的对象。</strong>如果你想要字符串化一个提交数据,请使用这个 <a href="#A_little_vanilla_framework">早期的纯 AJAX 例子</a>. 同时也要注意,尽管这个例子中有一些 <code>file</code> {{ HTMLElement("input") }} 字段,<strong>但当你通过</strong><strong> <code>FormData</code> API 提交一个表格时,也无须使用 {{domxref("FileReader")}} API</strong>: 文件被自动加载并上传。</div>
<h2 id="获取最后修改日期">获取最后修改日期</h2>
<pre class="brush: js notranslate">function getHeaderTime () {
console.log(this.getResponseHeader("Last-Modified")); /* 一个合法的 GMTString 日期或 null */
}
var oReq = new XMLHttpRequest();
oReq.open("HEAD" /* 仅需要头部信息(headers)时请使用 HEAD! */, "yourpage.html");
oReq.onload = getHeaderTime;
oReq.send();</pre>
<h3 id="最后修改日期改变后的操作">最后修改日期改变后的操作</h3>
<p>先创建两个函数:</p>
<pre class="brush: js notranslate">function getHeaderTime () {
var nLastVisit = parseFloat(window.localStorage.getItem('lm_' + this.filepath));
var nLastModif = Date.parse(this.getResponseHeader("Last-Modified"));
if (isNaN(nLastVisit) || nLastModif > nLastVisit) {
window.localStorage.setItem('lm_' + this.filepath, Date.now());
isFinite(nLastVisit) && this.callback(nLastModif, nLastVisit);
}
}
function ifHasChanged(sURL, fCallback) {
var oReq = new XMLHttpRequest();
oReq.open("HEAD" /* 使用 HEAD - 我们仅需要头部信息(headers)! */, sURL);
oReq.callback = fCallback;
oReq.filepath = sURL;
oReq.onload = getHeaderTime;
oReq.send();
}</pre>
<p>And to test:</p>
<pre class="brush: js notranslate">/* 测试一下这个文件:"yourpage.html"... */
ifHasChanged("yourpage.html", function (nModif, nVisit) {
console.log("The page '" + this.filepath + "' has been changed on " + (new Date(nModif)).toLocaleString() + "!");
});</pre>
<p>如果你想要了解 <strong><em>当前页面是否发生了改变,</em></strong>请阅读这篇文章:{{domxref("document.lastModified")}}.</p>
<h2 id="跨站的_XMLHttpRequest">跨站的 XMLHttpRequest</h2>
<p>现代浏览器可以通过执行 WebApps 工作小组通过的 <a href="/zh-CN/docs/Web/HTTP/Access_control_CORS">Access Control for Cross-Site Requests</a> 标注来支持跨站请求。只要服务器端的配置允许您从您的 Web 应用发送请求,就可以使用 <code>XMLHttpRequest</code> 。 否则,会抛出一个 <code>INVALID_ACCESS_ERR</code> 异常</p>
<h2 id="绕过缓存">绕过缓存</h2>
<p>一般地,如果缓存中有相应内容, <code>XMLHttpRequest</code> 会试图从缓存中读取内容。绕过缓存的方法见下述代码:</p>
<pre class="brush: js notranslate">var req = new XMLHttpRequest();
req.open('GET', url, false);
<strong>req.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;</strong>
req.send(null);</pre>
<div class="note"><strong>Note:</strong> 这个方法只工作于基于 Gecko内核的软件, 也就是通道属性是 Gecko-specific.</div>
<p>还有一个跨浏览器兼容的方法,就是给 URL 添加时间戳。请确保你酌情地添加了 "?" or "&" 。例如,将:</p>
<pre class="brush:plain notranslate">http://foo.com/bar.html -> http://foo.com/bar.html?12345
http://foo.com/bar.html?foobar=baz -> http://foo.com/bar.html?foobar=baz&12345
</pre>
<p>因为本地缓存都是以 URL 作为索引的,这样就可以使每个请求都是唯一的,也就可以这样来绕开缓存。</p>
<p>你也可以用下面的方法自动更改缓存:</p>
<pre class="brush:js notranslate">var oReq = new XMLHttpRequest();
oReq.open("GET", url + ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime());
oReq.send(null);</pre>
<h2 id="安全性">安全性</h2>
<p>{{fx_minversion_note(3, "Versions of Firefox prior to Firefox 3 allowed you to set the preference <code>capability.policy.<policyname>.XMLHttpRequest.open</policyname></code> to <code>allAccess</code> to give specific sites cross-site access. This is no longer supported.")}}</p>
<p>{{fx_minversion_note(5, "Versions of Firefox prior to Firefox 5 could use <code>netscape.security.PrivilegeManager.enablePrivilege(\"UniversalBrowserRead\");</code> to request cross-site access. This is no longer supported, even though it produces no warning and permission dialog is still presented.")}}</p>
<p>要启用跨站脚本,推荐的做法是对 XMLHttpRequest 的响应使用 the <code>Access-Control-Allow-Origin </code>的 HTTP 头。</p>
<h3 id="XMLHttpRequests_被停止">XMLHttpRequests 被停止</h3>
<p>如果你的 XMLHttpRequest 收到 <code>status=0</code> 和 <code>statusText=null</code> 的返回,这意味着请求无法执行。 就是<a href="http://www.w3.org/TR/XMLHttpRequest/#dom-xmlhttprequest-unsent">无法发送</a>. 一个可能导致的原因是当 <a href="http://www.w3.org/TR/XMLHttpRequest/#xmlhttprequest-origin"><code>XMLHttpRequest</code> origin</a> (创建的 XMLHttpRequest) 改变时,XMLHttpRequest 执行 <code>open()</code>.。这种情况是可能发生的,举个例子,我们在一个窗口的<code>onunload</code>事件中关闭XMLHttpRequest,但实际上在即将关闭窗口时,之前创建的XMLHttpRequest仍然在那里,最后当这个窗口失去焦点、另一个窗口获得焦点时,它还是发送了请求(也就是<code>open()</code>)。最有效的避免这个问题的方法是为新窗口的{{event("activate")}}事件设置一个监听器,一旦窗口关闭,它的{{event("unload")}}事件便触发。</p>
<h2 id="Worker">Worker</h2>
<p>设置 <code>overrideMimeType</code> 后在 {{domxref("Worker")}} 中无法正常工作,详见 {{bug(678057)}}。其他浏览器也许会以不同的手段处理。</p>
<h2 id="规范">规范</h2>
<table class="standard-table">
<thead>
<tr>
<th scope="col">Specification</th>
<th scope="col">Status</th>
<th scope="col">Comment</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{SpecName('XMLHttpRequest')}}</td>
<td>{{Spec2('XMLHttpRequest')}}</td>
<td>Live standard, latest version</td>
</tr>
</tbody>
</table>
<h2 id="浏览器兼容性">浏览器兼容性</h2>
<p>{{Compat("api.XMLHttpRequest")}}</p>
<h2 id="参考资料">参考资料</h2>
<ol>
<li><a href="/zh-CN/AJAX/Getting_Started">MDC AJAX introduction</a></li>
<li><a href="/zh-CN/docs/Web/API/XMLHttpRequest/HTML_in_XMLHttpRequest">HTML in XMLHttpRequest</a></li>
<li><a href="/zh-CN/HTTP_access_control">HTTP access control</a></li>
<li><a href="/zh-CN/How_to_check_the_security_state_of_an_XMLHTTPRequest_over_SSL">How to check the security state of an XMLHTTPRequest over SSL</a></li>
<li><a href="http://www.peej.co.uk/articles/rich-user-experience.html">XMLHttpRequest - REST and the Rich User Experience</a></li>
<li><a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/xmlsdk/html/xmobjxmlhttprequest.asp">Microsoft documentation</a></li>
<li><a href="http://developer.apple.com/internet/webcontent/xmlhttpreq.html">Apple developers' reference</a></li>
<li><a href="http://jibbering.com/2002/4/httprequest.html">"Using the XMLHttpRequest Object" (jibbering.com)</a></li>
<li><a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object: W3C Specification</a></li>
<li><a href="http://dev.w3.org/2006/webapi/progress/Progress.html">Web Progress Events specification</a></li>
<li><a href="http://www.bluishcoder.co.nz/2009/06/05/reading-ogg-files-with-javascript.html">Reading Ogg files with JavaScript (Chris Double)</a></li>
</ol>
|