ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ## 13.1 XMLHttpRequest对象 **XMLHttpRequest对象**用来在浏览器与服务器之间传送数据。 ~~~ var ajax = new XMLHttpRequest(); ajax.open('GET', 'http://www.example.com/page.php', true); ~~~ 上面代码向指定的服务器网址,发出GET请求。 然后,AJAX指定回调函数,监听通信状态(readyState属性)的变化。 ~~~ ajax.onreadystatechange = handleStateChange; ~~~ 一旦拿到服务器返回的数据,AJAX不会刷新整个网页,而是只更新相关部分,从而不打断用户正在做的事情。 注意,AJAX只能向同源网址(协议、域名、端口都相同)发出HTTP请求,如果发出跨源请求,就会报错。 虽然名字里面有XML,但是实际上,XMLHttpRequest可以报送各种数据,包括字符串和二进制,而且除了HTTP,它还支持通过其他协议传送(比如File和FTP)。 ### 13.1.1 XHR的用法 ~~~ var xhr = new XMLHttpRequest(); // 指定通信过程中状态改变时的回调函数 xhr.onreadystatechange = function(){ // 通信成功时,状态值为4 if (xhr.readyState === 4){ if (xhr.status === 200){ console.log(xhr.responseText); } else { console.error(xhr.statusText); } } }; xhr.onerror = function (e) { console.error(xhr.statusText); }; // open方式用于指定HTTP动词、请求的网址、是否异步 xhr.open('GET', '/endpoint', true); // 发送HTTP请求 xhr.send(null); ~~~ open方法的第三个参数是一个布尔值,表示是否为异步请求。如果设为false,就表示这个请求是同步的,下面是一个例子。 ~~~ var request = new XMLHttpRequest(); request.open('GET', '/bar/foo.txt', false); request.send(null); if (request.status === 200) { console.log(request.responseText); } ~~~ ### 13.1.2 XHR实例的属性 **1. readyState 与 onreadystatechange** * `readyState`是一个只读属性,用一个整数和对应的常量,表示XMLHttpRequest请求当前所处的状态。 ||| ||| | 0 (UNSEN)| 未初始化。表示XMLHttpRequest实例已经生成,但尚未调用open()方法。| | 1(OPENED)| 启动。已经调用open()方法,但未调用send()方法,仍然可以使用setRequestHeader(),设定HTTP请求的头信息。| | 2(HEADERS_RECEIVED)|发送。已经调用send()方法,并且头信息和状态码已经收到,但未收到相应。| | 3(LOADING)| 接受。表示正在接收服务器传来的body部分的数据,如果responseType属性是text或者空字符串,responseText就会包含已经收到的部分信息。| | 4(DONE)| 完成。表示服务器数据已经完全接收,或者本次接收已经失败了。| 在通信过程中,每当发生状态变化的时候,readyState属性的值就会发生改变。这个值每一次变化,都会触发`readyStateChange`事件。 * `onreadystatechange属性`指向一个回调函数,当readystatechange事件发生的时候,这个回调函数就会调用,并且XMLHttpRequest实例的readyState属性也会发生变化。 另外,如果使用abort()方法,终止XMLHttpRequest请求,onreadystatechange回调函数也会被调用。 ~~~ var 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("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get","example.txt",true); xhr.send(null); ~~~ 上面代码表示,只有readyState变为4时,才算确认请求已经成功,其他值都表示请求还在进行中。 必须在调用`open()`之前指定`onreadystatechange`事件处理程序才能确保跨浏览器兼容性。 **2. response 、responseType 、responseText与responseXML** * `response属性`为只读,返回接收到的数据体(即body部分)。它的类型可以是ArrayBuffer、Blob、Document、JSON对象、或者一个字符串,这由XMLHttpRequest.responseType属性的值决定。 如果本次请求没有成功或者数据不完整,该属性就会等于null。 * `responseType属性`用来指定服务器返回数据(xhr.response)的类型。 ||| ||| | “” | 字符串(默认值)| | arraybuffer | ArrayBuffer对象| | blob | Blob对象(二进制对象)| | document | Document对象| | json | JSON对象| | text | 字符串| text类型适合大多数情况,而且直接处理文本也比较方便,document类型适合返回XML文档的情况,blob类型适合读取二进制数据,比如图片文件。 ~~~ var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'blob'; xhr.onload = function(e) { if (this.status == 200) { var blob = new Blob([this.response], {type: 'image/png'}); // 或者 var blob = oReq.response; } }; xhr.send(); ~~~ 如果将这个属性设为ArrayBuffer,就可以按照数组的方式处理二进制数据。 ~~~ var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { var uInt8Array = new Uint8Array(this.response); for (var i = 0, len = binStr.length; i < len; ++i) { // var byte = uInt8Array[i]; } }; xhr.send(); ~~~ 如果将这个属性设为“json”,支持JSON的浏览器,就会自动对返回数据调用JSON.parse()方法。也就是说,你从`xhr.response`属性(注意,不是xhr.responseText属性)得到的不是文本,而是一个**JSON对象**。 XHR2支持Ajax的返回类型为文档,即xhr.responseType=”document” 。这意味着,对于那些打开`CORS`(跨域资源共享)的网站,我们可以直接用Ajax抓取网页,然后不用解析HTML字符串,直接对XHR回应进行DOM操作。 * `responseText属性`返回从服务器接收到的字符串,该属性为只读。如果本次请求没有成功或者数据不完整,该属性就会等于null。 如果服务器返回的数据格式是JSON,就可以使用responseText属性。 ~~~ var data = ajax.responseText; data = JSON.parse(data); ~~~ * `responseXML属性`返回从服务器接收到的Document对象,该属性为只读。如果本次请求没有成功,或者数据不完整,或者不能被解析为XML或HTML,该属性等于null。 返回的数据会被直接解析为DOM对象。 ~~~ /* 返回的XML文件如下 <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <book> <chapter id="1">(Re-)Introducing JavaScript</chapter> <chapter id="2">JavaScript in Action</chapter> </book> */ var data = ajax.responseXML; var chapters = data.getElementsByTagName('chapter'); ~~~ 如果服务器返回的数据,没有明示Content-Type头信息等于text/xml,可以使用overrideMimeType()方法,指定XMLHttpRequest对象将返回的数据解析为XML。 **3. status与statusText** * `status属性`为只读属性,表示本次请求所得到的HTTP状态码,它是一个整数。一般来说,如果通信成功的话,这个状态码是200。 ||| ||| | 200 | OK,访问正常| | 301 | Moved Permanently,永久移动| | 302 | Move temporarily,暂时移动| | 304 | Not Modified,未修改| | 307 | Temporary Redirect,暂时重定向| | 401 | Unauthorized,未授权| | 403 | Forbidden,禁止访问| | 404 | Not Found,未发现指定网址| | 500 | Internal Server Error,服务器发生错误| 基本上,只有2xx和304的状态码,表示服务器返回是正常状态。 ~~~ if (ajax.readyState == 4) { if ( (ajax.status >= 200 && ajax.status < 300) || (ajax.status == 304) ) { // Handle the response. } else { // Status error! } } ~~~ * `statusText属性`为只读属性,返回一个字符串,表示服务器发送的状态提示。不同于status属性,该属性包含**整个状态信息**,比如”200 OK“。 **4. timeout** * `timeout属性`等于一个整数,表示多少毫秒后,如果请求仍然没有得到结果,就会自动终止。如果该属性等于0,就表示没有时间限制。 ~~~ var xhr = new XMLHttpRequest(); xhr.ontimeout = function () { console.error("The request for " + url + " timed out."); }; xhr.onload = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { callback.apply(xhr, args); } else { console.error(xhr.statusText); } } }; xhr.open("GET", url, true); xhr.timeout = timeout; xhr.send(null); } ~~~ **5. 事件监听接口** XMLHttpRequest第一版,只能对onreadystatechange这一个事件指定回调函数。该事件对所有情况作出响应。 XMLHttpRequest第二版允许对更多的**事件指定回调函数**。 ||| |||| | onloadstart| 请求发出| | onprogress | 正在发送和加载数据| | onabort | 请求被中止,比如用户调用了abort()方法| | onerror | 请求失败| | onload| 请求成功完成| | ontimeout | 用户指定的时限到期,请求还未完成| | onloadend | 请求完成,不管成果或失败| ~~~ xhr.onload = function() { var responseText = xhr.responseText; console.log(responseText); // process the response. }; xhr.onerror = function() { console.log('There was an error!'); }; ~~~ 注意,如果发生网络错误(比如服务器无法连通),onerror事件无法获取报错信息,所以只能显示报错。 **6. withCredentials** * `withCredentials属性`是一个布尔值,表示跨域请求时,用户信息(比如Cookie和认证的HTTP头信息)是否会包含在请求之中,默认为false。即向example.com发出跨域请求时,不会发送example.com设置在本机上的Cookie(如果有的话)。 如果你需要通过跨域AJAX发送Cookie,需要打开withCredentials。 ~~~ xhr.withCredentials = true; ~~~ 为了让这个属性生效,服务器必须显式返回Access-Control-Allow-Credentials这个头信息。 ~~~ Access-Control-Allow-Credentials: true ~~~ `withCredentials属性`打开的话,不仅会发送Cookie,还会设置远程主机指定的Cookie。注意,此时你的脚本还是遵守同源政策,无法从document.cookie或者HTTP回应的头信息之中,读取这些Cookie。 ### 13.1.3 XHR实例的方法 **1. abort()** `abort方法`用来终止已经发出的HTTP请求。 ~~~ ajax.open('GET', 'http://www.example.com/page.php', true); var ajaxAbortTimer = setTimeout(function() { if (ajax) { ajax.abort(); ajax = null; } }, 5000); ~~~ 上面代码在发出5秒之后,终止一个AJAX请求。 **2. getAllResponseHeaders()** `getAllResponseHeaders方法`返回服务器发来的所有HTTP头信息。格式为字符串,每个头信息之间使用CRLF分隔,如果没有受到服务器回应,该属性返回null。 **3. getResponseHeader()** `getResponseHeader方法`返回HTTP头信息指定字段的值,如果还没有收到服务器回应或者指定字段不存在,则该属性为null。 ~~~ function getHeaderTime () { console.log(this.getResponseHeader("Last-Modified")); } var oReq = new XMLHttpRequest(); oReq.open("HEAD", "yourpage.html"); oReq.onload = getHeaderTime; oReq.send(); ~~~ 如果有多个字段同名,则它们的值会被连接为一个字符串,每个字段之间使用“逗号+空格”分隔。 **4. open()** `open方法`用于指定发送HTTP请求的参数,它的使用格式如下,一共可以接受五个参数。 ~~~ void open( string method, string url, optional boolean async, optional string user, optional string password ); ~~~ ||| ||| | method | 表示HTTP动词,比如“GET”、“POST”、“PUT”和“DELETE”。| | url | 表示请求发送的网址。| | async | 格式为布尔值,默认为true,表示请求是否为异步。如果设为false,则send()方法只有等到收到服务器返回的结果,才会有返回值。| | user | 表示用于认证的用户名,默认为空字符串。| | password | 表示用于认证的密码,默认为空字符串。| 如果对使用过open()方法的请求,再次使用这个方法,等同于调用abort()。 下面发送POST请求的例子。 ~~~ xhr.open('POST', encodeURI('someURL')); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onload = function() {}; xhr.send(encodeURI('dataString')); ~~~ 上面方法中,open方法向指定URL发出POST请求,send方法送出实际的数据。 下面是一个同步AJAX请求的例子。 ~~~ var request = new XMLHttpRequest(); request.open('GET', '/bar/foo.txt', false); request.send(null); if (request.status === 200) { console.log(request.responseText); } ~~~ **5. send()** `send方法`用于实际发出HTTP请求。如果不带参数,就表示HTTP请求只包含头信息,也就是只有一个URL,典型例子就是GET请求;如果带有参数,就表示除了头信息,还带有包含具体数据的信息体,典型例子就是POST请求。 ~~~ ajax.open('GET' , 'http://www.example.com/somepage.php?id=' + encodeURIComponent(id) , true ); // 等同于 var data = 'id=' + encodeURIComponent(id)); ajax.open('GET', 'http://www.example.com/somepage.php', true); ajax.send(data); ~~~ 上面代码中,GET请求的参数,可以作为查询字符串附加在URL后面,也可以作为send方法的参数。 下面是发送POST请求的例子。 ~~~ var data = 'email=' + encodeURIComponent(email) + '&password=' + encodeURIComponent(password); ajax.open('POST', 'http://www.example.com/somepage.php', true); ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); ajax.send(data); ~~~ 如果请求是异步的(默认为异步),该方法在发出请求后会立即返回。如果请求为同步,该方法只有等到收到服务器回应后,才会返回。 注意,所有XMLHttpRequest的监听事件,都必须在send()方法调用之前设定。 `send方法`的参数就是发送的数据。多种格式的数据,都可以作为它的参数。 ~~~ void send(); void send(ArrayBufferView data); void send(Blob data); void send(Document data); void send(String data); void send(FormData data); ~~~ 如果发送Document数据,在发送之前,数据会先被串行化。 发送二进制数据,最好使用`ArrayBufferView或Blob对象`,这使得通过Ajax上传文件成为可能。 下面是一个上传ArrayBuffer对象的例子。 ~~~ function sendArrayBuffer() { var xhr = new XMLHttpRequest(); var uInt8Array = new Uint8Array([1, 2, 3]); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... }; xhr.send(uInt8Array.buffer); } ~~~ **FormData类型**可以用于构造表单数据。 ~~~ var formData = new FormData(); formData.append('username', '张三'); formData.append('email', 'zhangsan@example.com'); formData.append('birthDate', 1940); var xhr = new XMLHttpRequest(); xhr.open("POST", "/register"); xhr.send(formData); ~~~ 上面的代码构造了一个formData对象,然后使用send方法发送。它的效果与点击下面表单的submit按钮是一样的。 ~~~ <form id='registration' name='registration' action='/register'> <input type='text' name='username' value='张三'> <input type='email' name='email' value='zhangsan@example.com'> <input type='number' name='birthDate' value='1940'> <input type='submit' onclick='return sendForm(this.form);'> </form> ~~~ FormData也可以将现有表单构造生成。 ~~~ var formElement = document.querySelector("form"); var request = new XMLHttpRequest(); request.open("POST", "submitform.php"); request.send(new FormData(formElement)); ~~~ FormData对象还可以对现有表单添加数据,这为我们操作表单提供了极大的灵活性。 ~~~ function sendForm(form) { var formData = new FormData(form); formData.append('csrf', 'e69a18d7db1286040586e6da1950128c'); var xhr = new XMLHttpRequest(); xhr.open('POST', form.action, true); xhr.onload = function(e) { // ... }; xhr.send(formData); return false; } var form = document.querySelector('#registration'); sendForm(form); ~~~ FormData对象也能用来模拟File控件,进行文件上传。 ~~~ function uploadFiles(url, files) { var formData = new FormData(); for (var i = 0, file; file = files[i]; ++i) { formData.append(file.name, file); // 可加入第三个参数,表示文件名 } var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function(e) { ... }; xhr.send(formData); // multipart/form-data } document.querySelector('input[type="file"]').addEventListener('change', function(e) { uploadFiles('/server', this.files); }, false); ~~~ FormData也可以加入JavaScript生成的文件。 ~~~ // 添加JavaScript生成的文件 var content = '<a id="a"><b id="b">hey!</b></a>'; var blob = new Blob([content], { type: "text/xml"}); formData.append("webmasterfile", blob); ~~~ **6. setRequestHeader()** `setRequestHeader方法`用于设置HTTP头信息。该方法必须在open()之后、send()之前调用。如果该方法多次调用,设定同一个字段,则每一次调用的值会被合并成一个单一的值发送。 ~~~ xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader('Content-Length', JSON.stringify(data).length); xhr.send(JSON.stringify(data)); ~~~ 上面代码首先设置头信息Content-Type,表示发送JSON格式的数据;然后设置Content-Length,表示数据长度;最后发送JSON数据。 **7. overrideMimeType** 该方法用来指定服务器返回数据的MIME类型。该方法必须在send()之前调用。 传统上,如果希望从服务器取回二进制数据,就要使用这个方法,人为将数据类型伪装成文本数据。 ~~~ var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); // 强制将MIME改为文本类型 xhr.overrideMimeType('text/plain; charset=x-user-defined'); xhr.onreadystatechange = function(e) { if (this.readyState == 4 && this.status == 200) { var binStr = this.responseText; for (var i = 0, len = binStr.length; i < len; ++i) { var c = binStr.charCodeAt(i); var byte = c & 0xff; // 去除高位字节,留下低位字节 } } }; xhr.send(); ~~~ 上面代码中,因为传回来的是二进制数据,首先用xhr.overrideMimeType方法强制改变它的MIME类型,伪装成文本数据。字符集必需指定为“x-user-defined”,如果是其他字符集,浏览器内部会强制转码,将其保存成UTF-16的形式。字符集“x-user-defined”其实也会发生转码,浏览器会在每个字节前面再加上一个字节(0xF700-0xF7ff),因此后面要对每个字符进行一次与运算(&),将高位的8个位去除,只留下低位的8个位,由此逐一读出原文件二进制数据的每个字节。 这种方法很麻烦,在XMLHttpRequest版本升级以后,一般采用指定responseType的方法。 ~~~ var xhr = new XMLHttpRequest(); xhr.onload = function(e) { var arraybuffer = xhr.response; // ... } xhr.open("GET", url); xhr.responseType = "arraybuffer"; xhr.send(); ~~~ ### 13.1.4 XHR实例的事件 **1. readyStateChange事件** readyState属性的值发生改变,就会触发readyStateChange事件。 我们可以通过onReadyStateChange属性,指定这个事件的回调函数,对不同状态进行不同处理。尤其是当状态变为4的时候,表示通信成功,这时回调函数就可以处理服务器传送回来的数据。 **2. progress事件** 上传文件时,XMLHTTPRequest对象的upload属性有一个progress,会不断返回上传的进度。 假定网页上有一个progress元素。 ~~~ <progress min="0" max="100" value="0">0% complete</progress> ~~~ 文件上传时,对upload属性指定progress事件回调函数,即可获得上传的进度。 ~~~ function upload(blobOrFile) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... }; // Listen to the upload progress. var progressBar = document.querySelector('progress'); xhr.upload.onprogress = function(e) { if (e.lengthComputable) { progressBar.value = (e.loaded / e.total) * 100; progressBar.textContent = progressBar.value; // Fallback for unsupported browsers. } }; xhr.send(blobOrFile); } upload(new Blob(['hello world'], {type: 'text/plain'})); ~~~ **3. load事件、error事件、abort事件** `load事件`表示服务器传来的数据接收完毕,`error事件`表示请求出错,`abort事件`表示请求被中断。 ~~~ var xhr = new XMLHttpRequest(); xhr.addEventListener("progress", updateProgress); xhr.addEventListener("load", transferComplete); xhr.addEventListener("error", transferFailed); xhr.addEventListener("abort", transferCanceled); xhr.open(); function updateProgress (oEvent) { if (oEvent.lengthComputable) { var percentComplete = oEvent.loaded / oEvent.total; // ... } 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."); } ~~~ **4. loadend事件** abort、load和error这三个事件,会伴随一个loadend事件,表示请求结束,但不知道其是否成功。 ~~~ req.addEventListener("loadend", loadEnd); function loadEnd(e) { alert("请求结束(不知道是否成功)"); } ~~~