使用 XMLHttpRequest
Baseline Widely available *
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
* Some parts of this feature may have varying levels of support.
在该教程中,我们将使用XMLHttpRequest
来发送 HTTP 请求以实现网站和服务器之间的数据交换。XMLHttpRequest
常见和晦涩的使用情况都将包含在例子中。
发送一个 HTTP 请求,需要创建一个 XMLHttpRequest
对象,打开一个 URL,最后发送请求。当所有这些事务完成后,该对象将会包含一些诸如响应主体或 HTTP status 的有用信息。
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();
请求类型
通过 XMLHttpRequest
生成的请求可以有两种方式来获取数据,异步模式或同步模式。请求的类型是由这个 XMLHttpRequest
对象的 open() 方法的第三个参数async
的值决定的。如果该参数的值为 false
,则该 XMLHttpRequest
请求以同步模式进行,否则该过程将以异步模式完成。这两种类型请求的详细讨论和指南可以在同步和异步请求页找到。
备注: 由于对用户体验的负面影响,从 Gecko 30.0 版本开始,在主线程上的同步请求已经被弃用。
备注: XMLHttpRequest
构造函数并不仅限于 XML 文档。它之所以使用“XML”开头是因为在它诞生之时,原先用于异步数据交换的主要格式便是 XML。
处理响应
W3C 规范定义了 XMLHttpRequest()
对象的几种类型的响应属性。这些属性告诉客户端关于 XMLHttpRequest
返回状态的重要信息。一些处理非文本返回类型的用例,可能包含下面章节所描述的一些操作和分析。
分析并操作 responseXML 属性
如果你使用 XMLHttpRequest
来获得一个远程的 XML 文档的内容,responseXML
属性将会是一个由 XML 文档解析而来的 DOM 对象,这很难被操作和分析。这里有五种主要的分析 XML 文档的方式:
- 使用 XPath 定位到文档的指定部分。
- 手动解析和序列化 XML 为字符串或对象。
- 使用 XMLSerializer 把 DOM 树序列化成字符串或文件。
- 如果你预先知道 XML 文档的内容,你可以使用 RegExp。如果你用
RegExp
扫描时受到换行符的影响,你也许想要删除所有的换行符。然而,这种方法是"最后手段",因为如果 XML 代码发生轻微变化,该方法将可能失败。
备注: 在 W3C XMLHttpRequest 规范中允许 HTML 通过 XMLHttpRequest.responseXML 属性进行解析。更多详细内容请阅读 HTML in XMLHttpRequest 。本条注意已在英文原文中更新。
备注: XMLHttpRequest
现在可以使用 responseXML
属性解释 HTML。请阅读 HTML in XMLHttpRequest 这篇文章了解相关用法。
解析和操作包含 HTML 文档的 responseText 属性
如果使用 XMLHttpRequest
从远端获取一个 HTML 页面,则所有 HTML 标记会以字符串的形式存放在 responseText 属性里,这样就使得操作和解析这些标记变得困难。解析这些 HTML 标记主要有三种方式:
处理二进制数据
尽管 XMLHttpRequest
一般用来发送和接收文本数据,但其实也可以发送和接收二进制内容。有许多经过良好测试的方法来强制使用 XMLHttpRequest
发送二进制数据。利用 XMLHttpRequest
对象的 overrideMimeType()
方法是一个解决方案,虽然它并不是一个标准方法。
var oReq = new XMLHttpRequest();
oReq.open("GET", url);
// 以二进制字符串形式检索未处理的数据
oReq.overrideMimeType("text/plain; charset=x-user-defined");
/* ... */
然而,自从 responseType
属性目前支持大量附加的内容类型后,已经出现了很多的现代技术,它们使得发送和接收二进制数据变得更加容易。
例如,考虑以下代码,它使用 "arraybuffer"
的 responseType
来将远程内容获取到一个存储原生二进制数据的 ArrayBuffer
对象中。
var oReq = new XMLHttpRequest();
oReq.onload = function (e) {
var arraybuffer = oReq.response; // 不是 responseText!
/* ... */
};
oReq.open("GET", url);
oReq.responseType = "arraybuffer";
oReq.send();
更多示例请参考 发送和接收二进制数据。
监测进度
XMLHttpRequest
提供了各种在请求被处理期间发生的事件以供监听。这包括定期进度通知、错误通知,等等。
支持 DOM 的 progress 事件监测之于 XMLHttpRequest
传输,遵循 Web API 进度事件规范:这些事件实现了 ProgressEvent
接口。
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.");
}
第 3-6 行为多种事件添加了事件监听,这些事件在使用 XMLHttpRequest
执行数据传输时被发出。
备注:
你需要在请求调用 open()
之前添加事件监听。否则 progress
事件将不会被触发。
在上一个例子中,progress 事件被指定由 updateProgress()
函数处理,并接收到传输的总字节数和已经传输的字节数,它们分别在事件对象的 total
和 loaded
属性里。但是如果 lengthComputable
属性的值是 false,那么意味着总字节数是未知并且 total 的值为零。
progress 事件同时存在于下载和上传的传输。下载相关事件在 XMLHttpRequest
对象上被触发,就像上面的例子一样。上传相关事件在 XMLHttpRequest.upload
对象上被触发,像下面这样:
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();
备注:
progress 事件在使用 file:
协议的情况下是无效的。
备注: 从 Gecko 9.0 开始,进度事件现在可以依托于每一个传入的数据块,包括进度事件被触发前在已经接受了最后一个数据包且连接已经被关闭的情况下接收到的最后一个块。这种情况下,当该数据包的 load 事件发生时 progress 事件会被自动触发。这使你可以只关注 progress 事件就可以可靠的监测进度。
备注:
在 Gecko 12.0 中,当 responseType
为 "moz-blob" 时,如果你的 progress 事件被触发,则响应的值是一个包含了接收到的数据的 Blob
。
使用 loadend
事件可以侦测到所有的三种加载结束条件(abort
、load
,或 error
):
req.addEventListener("loadend", loadEnd);
function loadEnd(e) {
console.log(
"The transfer finished (although we don't know if it succeeded or not).",
);
}
需要注意的是,没有方法可以确切的知道 loadend
事件接收到的信息是来自何种条件引起的操作终止;但是你可以在所有传输结束的时候使用这个事件处理。
提交表单和上传文件
XMLHttpRequest
的实例有两种方式提交表单:
- 使用 AJAX
- 使用
FormData
API
第二种方式(使用 FormData
API)是最简单最快捷的,但是缺点是被收集的数据无法使用 JSON.stringify() 转换为一个 JSON 字符串。
只使用 AJAX 则更为复杂,但也更灵活、更强大。
仅使用 XMLHttpRequest
在大多数用例中,提交表单时即便不使用 FormData
API 也不会要求其他的 API。唯一的例外情况是,如果你要上传一个或多个文件,你需要额外的 FileReader
API。
提交方法简介
- 使用
POST
方法,并将enctype
属性设置为application/x-www-form-urlencoded
(默认) - 使用
POST
方法,并将enctype
属性设置为text/plain
- 使用
POST
方法,并将enctype
属性设置为multipart/form-data
- 使用
GET
方法(这种情况下enctype
属性会被忽略)
现在,我们提交一个表单,它里面有两个字段,分别被命名为 foo
和 baz
。如果你用 POST
方法,那么服务器将会接收到一个字符串类似于下面三种情况之一,其中的区别依赖于你采用何种编码类型:
- 方法:
POST
;编码类型:application/x-www-form-urlencoded
(默认):
Content-Type: application/x-www-form-urlencoded foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A
-
方法:
POST
;编码类型:text/plain
:Content-Type: text/plain foo=bar baz=The first line. The second line.
-
方法:
POST
;编码类型:multipart/form-data
: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--
相反的,如果你用 GET
方法,像下面这样的字符串将被简单的附加到 URL:
?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.
一个小框架
所有这些事情都是由浏览器在你提交一个 的时候自动完成的。但是如果你想要用 JavaScript 做同样的事情,你不得不告诉解释器所有的事。那么,如何发送表单这件事在使用纯粹的 AJAX 时会复杂到无法在这里解释清楚。基于这个原因,我们提供一个完整的(但仍然教条的)框架,它可以使用所有的四种提交方式,甚至上传文件:
Sending forms with pure AJAX – MDN
Sending forms with pure AJAX
Using the GET method
Using the POST method
Enctype: application/x-www-form-urlencoded (default)
Enctype: text/plain
Enctype: multipart/form-data
要测试它的话,创建一个名为 register.php
的页面(作为示例表单的 action
属性)并且只输入以下内容:
激活这些代码的语法很简单:
AJAXSubmit(myForm);
备注:
该框架使用 FileReader
API 进行文件的上传。这是一个较新的 API 并且还未在 IE9 及以下版本的浏览器中实现。因此,使用 AJAX 上传仍是一项实验性的技术。如果你不需要上传 二进制文件,该框架在大多数浏览器中运行良好。
备注:
发送二进制内容的最佳途径是通过 ArrayBuffers
或 Blobs
结合 send()
方法甚至 FileReader
API 的 readAsArrayBuffer()
方法。但是,自从该脚本的目的变成处理 可字符串化 的原始数据以来,我们使用 sendAsBinary()
方法结合 FileReader
API 的 readAsBinaryString()
方法。同样地,上述脚本仅当你处理小文件时行之有效。如果不打算上传二进制内容,就考虑使用 FormData
API 来替代。
备注:
非标准的 sendAsBinary
方法从 Gecko 31 开始将会废弃并且会很快被移除。标准方法 send(Blob data)
将会取而代之。
使用 FormData 对象
FormData
构造函数能使你编译一个键/值对的集合,然后使用 XMLHttpRequest
发送出去。其主要用于发送表格数据,但是也能被单独用来传输表格中用户指定的数据。传输的数据格式与表格使用 submit()
方法发送数据的格式一致,如果该表格的编码类型被设为 "multipart/form-data"。FormData 对象可以被结合 XMLHttpRequest
的多种方法利用。例如,想了解如何利用 FormData 与 XMLHttpRequest,请转到使用 FormData 对象页面。为了说教的目的,这里有一个早期的示例,被转译成了使用 FormData
API 的形式。注意以下代码片段:
Sending forms with FormData – MDN
Sending forms with FormData
Using the GET method
Using the POST method
Enctype: application/x-www-form-urlencoded (default)
Enctype: text/plain
The text/plain encoding is not supported by the FormData API.
Enctype: multipart/form-data
备注:
如之前所述,FormData
对象并不是 可字符串化 (stringifiable) 的对象。如果你想要字符串化一个提交数据,请使用这个 早期的纯 AJAX 例子. 同时也要注意,尽管这个例子中有一些 file
字段,但当你通过
FormData
API 提交一个表格时,也无须使用 FileReader
API: 文件被自动加载并上传。
获取最后修改日期
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();
最后修改日期改变后的操作
先创建两个函数:
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();
}
And to test:
/* 测试一下这个文件:"yourpage.html"... */
ifHasChanged("yourpage.html", function (nModif, nVisit) {
console.log(
"The page '" +
this.filepath +
"' has been changed on " +
new Date(nModif).toLocaleString() +
"!",
);
});
如果你想要了解当前页面是否发生了改变,请阅读这篇文章:document.lastModified
。
跨站的 XMLHttpRequest
现代浏览器通过实现跨源资源共享(CORS)标准来支持跨站请求。只要服务器端的配置允许你从你的 Web 应用发送请求,就可以使用 XMLHttpRequest
。否则,会抛出一个 INVALID_ACCESS_ERR
异常
绕过缓存
有一个跨浏览器兼容的方法,就是给 URL 添加时间戳。请确保你酌情地添加了 "?" or "&" 。例如,将:
http://example.com/bar.html -> http://example.com/bar.html?12345 http://example.com/bar.html?foobar=baz -> http://example.com/bar.html?foobar=baz&12345
因为本地缓存都是以 URL 作为索引的,这样就可以使每个请求都是唯一的,也就可以这样来绕开缓存。
你也可以用下面的方法自动更改缓存:
const req = new XMLHttpRequest();
req.open("GET", url + (/\?/.test(url) ? "&" : "?") + new Date().getTime());
req.send(null);
安全性
要启用跨站脚本,推荐的做法是对 XMLHttpRequest 的响应使用 Access-Control-Allow-Origin
的 HTTP 标头。
XMLHttpRequests 被停止
如果你的 XMLHttpRequest 收到 status=0
和 statusText=null
的返回,这意味着请求无法执行。就是未被发送的(UNSENT
)。一个可能导致的原因是在 XMLHttpRequest 在执行 open()
时,XMLHttpRequest
的来源发生了改变。这种情况是可能发生的,例如,我们在一个窗口的 onunload 事件触发时在进行一个 XMLHttpRequest,之前创建的 XMLHttpRequest 仍然在那里,最后当这个窗口失去焦点、另一个窗口获得焦点时,它还是发送了请求(也就是 open()
)。最有效的避免这个问题的方法是在关闭的窗口触发 unload
事件时为新窗口的 DOMActivate
事件设置一个监听器。
规范
Specification |
---|
XMLHttpRequest # interface-xmlhttprequest |