[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("請求結束(不知道是否成功)");
}
~~~
- 前言
- 第一章 JavaScript簡介
- 第三章 基本概念
- 3.1-3.3 語法、關鍵字和變量
- 3.4 數據類型
- 3.5-3.6 操作符、流控制語句(暫略)
- 3.7函數
- 第四章 變量的值、作用域與內存問題
- 第五章 引用類型
- 5.1 Object類型
- 5.2 Array類型
- 5.3 Date類型
- 5.4 基本包裝類型
- 5.5 單體內置對象
- 第六章 面向對象的程序設計
- 6.1 理解對象
- 6.2 創建對象
- 6.3 繼承
- 第七章 函數
- 7.1 函數概述
- 7.2 閉包
- 7.3 私有變量
- 第八章 BOM
- 8.1 window對象
- 8.2 location對象
- 8.3 navigator、screen與history對象
- 第九章 DOM
- 9.1 節點層次
- 9.2 DOM操作技術
- 9.3 DOM擴展
- 9.4 DOM2和DOM3
- 第十章 事件
- 10.1 事件流
- 10.2 事件處理程序
- 10.3 事件對象
- 10.4 事件類型
- 第十一章 JSON
- 11.1-11.2 語法與序列化選項
- 第十二章 正則表達式
- 12.1 創建正則表達式
- 12.2-12.3 模式匹配與RegExp對象
- 第十三章 Ajax
- 13.1 XMLHttpRequest對象
- 你不知道的JavaScript
- 一、作用域與閉包
- 1.1 作用域
- 1.2 詞法作用域
- 1.3 函數作用域與塊作用域
- 1.4 提升
- 1.5 作用域閉包
- 二、this與對象原型
- 2.1 關于this
- 2.2 全面解析this
- 2.3 對象
- 2.4 混合對象“類”
- 2.5 原型
- 2.6 行為委托
- 三、類型與語法
- 3.1 類型
- 3.2 值
- 3.3 原生函數