所有现代浏览器都通过XMLHttpRequest构造函数原生支持XHR对象:
let xhr = new XMLHttpRequest();
24.1.1 使用XHR
使用XHR对象首先要调用open()方法,这个方法接收3个参数:
请求类型(”get”、”post”等)、请求URL,以及表示请求是否异步的布尔值。
下面是一个例子:
xhr.open('get', 'example.php', false);
可以向example.php发送一个同步的GET请求。
关于这行代码需要说明几点:
首先,这里的URL是相对于代码所在页面的,当然也可以使用绝对URL;
其次,调用open()不会实际发送请求,只是为发送请求做好准备。
注:只能访问同源URL,也就是域名相同、端口相同、协议相同。
如果请求的URL与发送请求的页面在任何方面有所不同,则会抛出安全错误。
要发送定义好的请求,必须像下面这样调用send()方法:
xhr.open('get', 'example.php', false);xhr.send(null);
send()方法接收一个参数,是作为请求体发送的数据。
如果不需要发送请求体,则必须传null,因为这个参数在某些浏览器中是必需的。
调用send()之后,请求就会发送到服务器。
因为这个请求是同步的,所以JavaScript代码会等待服务器响应之后再继续执行。
收到响应后,XHR对象的以下属性会被填充上数据:
❑ responseText:作为响应体返回的文本。
❑ responseXML:如果响应的内容类型是”text/xml”或”application/xml”,那就是包含响应数据的XML DOM文档。
❑ status:响应的HTTP状态。
❑ statusText:响应的HTTP状态描述。
收到响应后,第一步要检查status属性以确保响应成功返回。
一般来说,HTTP状态码为2xx表示成功。
此时,responseText或responseXML(如果内容类型正确)属性中会有内容。
如果HTTP状态码是304,则表示资源未修改过,是从浏览器缓存中直接拿取的。
当然这也意味着响应有效。
为确保收到正确的响应,应该检查这些状态,
如下所示:
xhr.open('get', 'example.php', false);xhr.send(null);if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {alert(xhr.responseType);} else {alert ('请求不成功:' + xhr.status);}
为确定下一步该执行什么操作,最好检查status而不是statusText属性,因为后者已经被证明在跨浏览器的情况下不可靠。
无论是什么响应内容类型,responseText属性始终会保存响应体,而responseXML则对于非XML数据是null。
虽然可以像前面的例子一样发送同步请求,但多数情况下最好使用异步请求,这样可以不阻塞JavaScript代码继续执行。
XHR对象有一个readyState属性,表示当前处在请求/响应过程的哪个阶段。
这个属性有如下可能的值:
❑ 0:未初始化(Uninitialized)。尚未调用open()方法。
❑ 1:已打开(Open)。已调用open()方法,尚未调用send()方法。
❑ 2:已发送(Sent)。已调用send()方法,尚未收到响应。
❑ 3:接收中(Receiving)。已经收到部分响应。
❑ 4:完成(Complete)。已经收到所有响应,可以使用了。
每次readyState从一个值变成另一个值,都会触发readystatechange事件。
可检查readyState的值。一般来说,我们唯一关心的readyState值是4,表示数据已就绪。
为保证跨浏览器兼容,onreadystatechange事件处理程序应该在调用open()之前赋值。
来看下面的例子:
let xhr = new XMLHttpRequest();xhr.onreadystatechange = function() {if (xhr.readyState == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {alert(xhr.responseText);} else {alert('请求不成功:' + xhr.status);}}};xhr.open('get', 'example.php', false);xhr.send(null);
在收到响应之前如果想取消异步请求,可以调用abort()方法:
xhr.abort();
调用这个方法后,XHR对象会停止触发事件,并阻止访问这个对象上任何与响应相关的属性。中断请求后,应该取消对XHR对象的引用。由于内存问题,不推荐重用XHR对象。
24.1.2 HTTP头部
每个HTTP请求和响应都会携带一些头部字段,这些字段可能对开发者有用。
XHR对象会通过一些方法暴露与请求和响应相关的头部字段。
默认情况下,XHR请求会发送以下头部字段:
❑ Accept:浏览器可以处理的内容类型。
❑ Accept-Charset:浏览器可以显示的字符集。
❑ Accept-Encoding:浏览器可以处理的压缩编码类型。
❑ Accept-Language:浏览器使用的语言。
❑ Connection:浏览器与服务器的连接类型。
❑ Cookie:页面中设置的Cookie。
❑ Host:发送请求的页面所在的域。
❑ Referer:发送请求的页面的URI。注意,这个字段在HTTP规范中就拼错了,所以考虑到兼容性也必须将错就错。(正确的拼写应该是Referrer。)
❑ User-Agent:浏览器的用户代理字符串。
虽然不同浏览器发送的确切头部字段可能各不相同,但这些通常都是会发送的。
如果需要发送额外的请求头部,可以使用setRequestHeader()方法。
这个方法接收两个参数:头部字段的名称和值。为保证请求头部被发送,必须在open()之后、send()之前调用setRequestHeader()
24.1.3 GET请求
GET请求,用于向服务器查询某些信息。必要时,需要在GET请求的URL后面添加查询字符串参数。
发送GET请求最常见的一个错误是查询字符串格式不对。查询字符串中的每个名和值都必须使用encodeURIComponent()编码,所有名/值对必须以和号(&)分隔
可以使用以下函数将查询字符串参数添加到现有的URL末尾:
function addURLParam(url, name, value) {url += (url.indexOf("?") === -1 ? "?" : "&");url += encodeURIComponent(name) + "=" + encodeURIComponent(value);return url;}
定义了一个addURLParam()函数,它接收3个参数:
要添加查询字符串的URL、查询参数和参数值。
首先,这个函数会检查URL中是否已经包含问号(以确定是否已经存在其他参数)。如果没有,则加上一个问号;否则就加上一个和号。
然后,分别对参数名和参数值进行编码,并添加到URL末尾。
最后一步是返回更新后的URL。
可以使用这个函数构建请求URL,
如下面的例子所示:
let url = 'example.php';// 添加参数url = addURLParam(url, 'name', 'Nick');url = addURLParam(url, 'book', 'JSBook');// 初始化请求xhr.open('get', url, false);
24.1.4 POST请求
POST请求,用于向服务器发送应该保存的数据。
每个POST请求都应该在请求体中携带提交的数据,而GET请求则不然。
POST请求的请求体可以包含非常多的数据,而且数据可以是任意格式。
要初始化POST请求,open()方法的第一个参数要传”post”
xhr.open('post', 'example.php', true);
接下来就是要给send()方法传入要发送的数据。因为XHR最初主要设计用于发送XML,所以可以传入序列化之后的XML DOM文档作为请求体。当然,也可以传入任意字符串。
默认情况下,对服务器而言,POST请求与提交表单是不一样的。服务器逻辑需要读取原始POST数据才能取得浏览器发送的数据。
不过,可以使用XHR模拟表单提交。为此,
第一步需要把Content-Type头部设置为”application/x-www-formurlencoded”,这是提交表单时使用的内容类型。
第二步是创建对应格式的字符串。POST数据此时使用与查询字符串相同的格式。
如果网页中确实有一个表单需要序列化并通过XHR发送到服务器,则可以使用第14章的serialize()函数来创建相应的字符串
function submitData() {let xhr = new XMLHttpRequest();xhr.onreadystatechange = function() {if (xhr.readyState == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {alert(xhr.responseText);} else {alert('请求不成功:' + xhr.status);}}};xhr.open('post', 'postexample.php', true);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');let form = document.getElementById('user-info');xhr.send(serialize(form));}
来自ID为”user-info”的表单中的数据被序列化之后发送给了服务器。
注:POST请求相比GET请求要占用更多资源。从性能方面说,发送相同数量的数据,GET请求比POST请求要快两倍。
24.1.5 XMLHttpRequest Level 2
并非所有浏览器都实现了XMLHttpRequest Level 2的所有部分,但所有浏览器都实现了其中部分功能。
1.FormData类型
现代Web应用程序中经常需要对表单数据进行序列化,因此XMLHttpRequestLevel 2新增了FormData类型。
FormData类型便于表单序列化,也便于创建与表单类似格式的数据然后通过XHR发送。
下面的代码创建了一个FormData对象,并填充了一些数据:
let data = new FormData();data.append('name', 'Nick');
append()方法接收两个参数:键和值,相当于表单字段名称和该字段的值。
可以像这样添加任意多个键/值对数据。
此外,通过直接给FormData构造函数传入一个表单元素,也可以将表单中的数据作为键/值对填充进去:
let data = new FormData(document.forms[0]);
有了FormData实例,可以像下面这样直接传给XHR对象的send()方法:
let xhr = new XMLHttpRequest();xhr.onreadystatechange = function() {if (xhr.readyState == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {alert(xhr.responseText);} else {alert('请求不成功:' + xhr.status);}}};xhr.open('post', 'postexample.php', true);let form = document.getElementById('user-info');xhr.send(new FormData(form));
使用FormData的另一个方便之处是不再需要给XHR对象显式设置任何请求头部了。XHR对象能够识别作为FormData实例传入的数据类型并自动配置相应的头部。
2.超时
XHR对象增加了timeout属性,表示发送请求后等待多少毫秒,如果响应不成功就中断请求。
在给timeout属性设置了一个时间,且在该时间过后没有收到响应时,XHR对象就会触发timeout事件,调用ontimeout事件处理程序。
let xhr = new XMLHttpRequest();xhr.onreadystatechange = function() {if (xhr.readyState == 4) {try {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {alert(xhr.responseText);} else {alert('请求不成功:' + xhr.status);}} catch(ex) {// 假设由ontimeout处理}}};xhr.open('post', 'postexample.php', true);xhr.timeout = 1000; // 设置1秒超时xhr.ontimeout = function() {alert('请求未在1秒内返回结果');};xhr.send(null);
演示了使用timeout设置超时。给timeout设置1000毫秒意味着,如果请求没有在1秒钟内返回则会中断。此时则会触发ontimeout事件处理程序,readyState仍然会变成4,因此也会调用onreadystatechange事件处理程序。不过,如果在超时之后访问status属性则会发生错误。
为做好防护,可以把检查status属性的代码封装在try/catch语句中。
3.overrideMimeType()方法
overrideMimeType()方法,用于重写XHR响应的MIME类型。
因为响应返回的MIME类型决定了XHR对象如何处理响应,所以如果有办法覆盖服务器返回的类型,那么是有帮助的。
假设服务器实际发送了XML数据,但响应头设置的MIME类型是text/plain。
结果就会导致虽然数据是XML,但responseXML属性值是null。
此时调用overrideMimeType()可保证将响应当成XML而不是纯文本来处理:
let xhr = new XMLHttpRequest();xhr.open('get', 'text.php', true);xhr.overrideMimeType('text/xml');xhr.send(null);
强制让XHR把响应当成XML而不是纯文本来处理。为了正确覆盖响应的MIME类型,必须在调用send()之前调用overrideMimeType()。
