簡介
瀏覽器與服務(wù)器之間,采用 HTTP 協(xié)議通信。用戶在瀏覽器地址欄鍵入一個(gè)網(wǎng)址,或者通過網(wǎng)頁表單向服務(wù)器提交內(nèi)容,這時(shí)瀏覽器就會向服務(wù)器發(fā)出 HTTP 請求。
1999年,微軟公司發(fā)布 IE 瀏覽器5.0版,第一次引入新功能:允許 JavaScript 腳本向服務(wù)器發(fā)起 HTTP 請求。這個(gè)功能當(dāng)時(shí)并沒有引起注意,直到2004年 Gmail 發(fā)布和2005年 Google Map 發(fā)布,才引起廣泛重視。2005年2月,AJAX 這個(gè)詞第一次正式提出,它是 Asynchronous JavaScript and XML 的縮寫,指的是通過 JavaScript 的異步通信,從服務(wù)器獲取 XML 文檔從中提取數(shù)據(jù),再更新當(dāng)前網(wǎng)頁的對應(yīng)部分,而不用刷新整個(gè)網(wǎng)頁。后來,AJAX 這個(gè)詞就成為 JavaScript 腳本發(fā)起 HTTP 通信的代名詞,也就是說,只要用腳本發(fā)起通信,就可以叫做 AJAX 通信。W3C 也在2006年發(fā)布了它的國際標(biāo)準(zhǔn)。
具體來說,AJAX 包括以下幾個(gè)步驟。
- 創(chuàng)建 XMLHttpRequest 實(shí)例
- 發(fā)出 HTTP 請求
- 接收服務(wù)器傳回的數(shù)據(jù)
- 更新網(wǎng)頁數(shù)據(jù)
概括起來,就是一句話,AJAX 通過原生的XMLHttpRequest
對象發(fā)出 HTTP 請求,得到服務(wù)器返回的數(shù)據(jù)后,再進(jìn)行處理?,F(xiàn)在,服務(wù)器返回的都是 JSON 格式的數(shù)據(jù),XML 格式已經(jīng)過時(shí)了,但是 AJAX 這個(gè)名字已經(jīng)成了一個(gè)通用名詞,字面含義已經(jīng)消失了。
XMLHttpRequest
對象是 AJAX 的主要接口,用于瀏覽器與服務(wù)器之間的通信。盡管名字里面有XML
和Http
,它實(shí)際上可以使用多種協(xié)議(比如file
或ftp
),發(fā)送任何格式的數(shù)據(jù)(包括字符串和二進(jìn)制)。
XMLHttpRequest
本身是一個(gè)構(gòu)造函數(shù),可以使用new
命令生成實(shí)例。它沒有任何參數(shù)。
var xhr = new XMLHttpRequest();
一旦新建實(shí)例,就可以使用open()
方法指定建立 HTTP 連接的一些細(xì)節(jié)。
xhr.open('GET', 'http://www.example.com/page.php', true);
上面代碼指定使用 GET 方法,跟指定的服務(wù)器網(wǎng)址建立連接。第三個(gè)參數(shù)true
,表示請求是異步的。
然后,指定回調(diào)函數(shù),監(jiān)聽通信狀態(tài)(readyState
屬性)的變化。
xhr.onreadystatechange = handleStateChange;
function handleStateChange() {
// ...
}
上面代碼中,一旦XMLHttpRequest
實(shí)例的狀態(tài)發(fā)生變化,就會調(diào)用監(jiān)聽函數(shù)handleStateChange
最后使用send()
方法,實(shí)際發(fā)出請求。
xhr.send(null);
上面代碼中,send()
的參數(shù)為null
,表示發(fā)送請求的時(shí)候,不帶有數(shù)據(jù)體。如果發(fā)送的是 POST 請求,這里就需要指定數(shù)據(jù)體。
一旦拿到服務(wù)器返回的數(shù)據(jù),AJAX 不會刷新整個(gè)網(wǎng)頁,而是只更新網(wǎng)頁里面的相關(guān)部分,從而不打斷用戶正在做的事情。
注意,AJAX 只能向同源網(wǎng)址(協(xié)議、域名、端口都相同)發(fā)出 HTTP 請求,如果發(fā)出跨域請求,就會報(bào)錯(cuò)(詳見《同源政策》和《CORS 通信》兩章)。
下面是XMLHttpRequest
對象簡單用法的完整例子。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
// 通信成功時(shí),狀態(tài)值為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);
};
xhr.open('GET', '/endpoint', true);
xhr.send(null);
XMLHttpRequest 的實(shí)例屬性
XMLHttpRequest.readyState
XMLHttpRequest.readyState
返回一個(gè)整數(shù),表示實(shí)例對象的當(dāng)前狀態(tài)。該屬性只讀。它可能返回以下值。
- 0,表示 XMLHttpRequest 實(shí)例已經(jīng)生成,但是實(shí)例的
open()
方法還沒有被調(diào)用。 - 1,表示
open()
方法已經(jīng)調(diào)用,但是實(shí)例的send()
方法還沒有調(diào)用,仍然可以使用實(shí)例的setRequestHeader()
方法,設(shè)定 HTTP 請求的頭信息。 - 2,表示實(shí)例的
send()
方法已經(jīng)調(diào)用,并且服務(wù)器返回的頭信息和狀態(tài)碼已經(jīng)收到。 - 3,表示正在接收服務(wù)器傳來的數(shù)據(jù)體(body 部分)。這時(shí),如果實(shí)例的
responseType
屬性等于text
或者空字符串,responseText
屬性就會包含已經(jīng)收到的部分信息。 - 4,表示服務(wù)器返回的數(shù)據(jù)已經(jīng)完全接收,或者本次接收已經(jīng)失敗。
通信過程中,每當(dāng)實(shí)例對象發(fā)生狀態(tài)變化,它的readyState
屬性的值就會改變。這個(gè)值每一次變化,都會觸發(fā)readyStateChange
事件。
var xhr = new XMLHttpRequest();
if (xhr.readyState === 4) {
// 請求結(jié)束,處理服務(wù)器返回的數(shù)據(jù)
} else {
// 顯示提示“加載中……”
}
上面代碼中,xhr.readyState
等于4
時(shí),表明腳本發(fā)出的 HTTP 請求已經(jīng)完成。其他情況,都表示 HTTP 請求還在進(jìn)行中。
XMLHttpRequest.onreadystatechange
XMLHttpRequest.onreadystatechange
屬性指向一個(gè)監(jiān)聽函數(shù)。readystatechange
事件發(fā)生時(shí)(實(shí)例的readyState
屬性變化),就會執(zhí)行這個(gè)屬性。
另外,如果使用實(shí)例的abort()
方法,終止 XMLHttpRequest 請求,也會造成readyState
屬性變化,導(dǎo)致調(diào)用XMLHttpRequest.onreadystatechange
屬性。
下面是一個(gè)例子。
var xhr = new XMLHttpRequest();
xhr.open( 'GET', 'http://example.com' , true );
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4 || xhr.status !== 200) {
return;
}
console.log(xhr.responseText);
};
xhr.send();
XMLHttpRequest.response
XMLHttpRequest.response
屬性表示服務(wù)器返回的數(shù)據(jù)體(即 HTTP 回應(yīng)的 body 部分)。它可能是任何數(shù)據(jù)類型,比如字符串、對象、二進(jìn)制對象等等,具體的類型由XMLHttpRequest.responseType
屬性決定。該屬性只讀。
如果本次請求沒有成功或者數(shù)據(jù)不完整,該屬性等于null
。但是,如果responseType
屬性等于text
或空字符串,在請求沒有結(jié)束之前(readyState
等于3的階段),response
屬性包含服務(wù)器已經(jīng)返回的部分?jǐn)?shù)據(jù)。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
handler(xhr.response);
}
}
XMLHttpRequest.responseType
XMLHttpRequest.responseType
屬性是一個(gè)字符串,表示服務(wù)器返回?cái)?shù)據(jù)的類型。這個(gè)屬性是可寫的,可以在調(diào)用open()
方法之后、調(diào)用send()
方法之前,設(shè)置這個(gè)屬性的值,告訴瀏覽器如何解讀返回的數(shù)據(jù)。如果responseType
設(shè)為空字符串,就等同于默認(rèn)值text
。
XMLHttpRequest.responseType
屬性可以等于以下值。
- ""(空字符串):等同于
text
,表示服務(wù)器返回文本數(shù)據(jù)。 - "arraybuffer":ArrayBuffer 對象,表示服務(wù)器返回二進(jìn)制數(shù)組。
- "blob":Blob 對象,表示服務(wù)器返回二進(jìn)制對象。
- "document":Document 對象,表示服務(wù)器返回一個(gè)文檔對象。
- "json":JSON 對象。
- "text":字符串。
上面幾種類型之中,text
類型適合大多數(shù)情況,而且直接處理文本也比較方便。document
類型適合返回 HTML / XML 文檔的情況,這意味著,對于那些打開 CORS 的網(wǎng)站,可以直接用 Ajax 抓取網(wǎng)頁,然后不用解析 HTML 字符串,直接對抓取回來的數(shù)據(jù)進(jìn)行 DOM 操作。blob
類型適合讀取二進(jìn)制數(shù)據(jù),比如圖片文件。
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([xhr.response], {type: 'image/png'});
// 或者
var blob = xhr.response;
}
};
xhr.send();
如果將這個(gè)屬性設(shè)為ArrayBuffer
,就可以按照數(shù)組的方式處理二進(jìn)制數(shù)據(jù)。
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 = uInt8Array.length; i < len; ++i) {
// var byte = uInt8Array[i];
}
};
xhr.send();
如果將這個(gè)屬性設(shè)為json
,瀏覽器就會自動對返回?cái)?shù)據(jù)調(diào)用JSON.parse()
方法。也就是說,從xhr.response
屬性(注意,不是xhr.responseText
屬性)得到的不是文本,而是一個(gè) JSON 對象。
XMLHttpRequest.responseText
XMLHttpRequest.responseText
屬性返回從服務(wù)器接收到的字符串,該屬性為只讀。只有 HTTP 請求完成接收以后,該屬性才會包含完整的數(shù)據(jù)。
var xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);
xhr.responseType = 'text';
xhr.onload = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send(null);
XMLHttpRequest.responseXML
XMLHttpRequest.responseXML
屬性返回從服務(wù)器接收到的 HTML 或 XML 文檔對象,該屬性為只讀。如果本次請求沒有成功,或者收到的數(shù)據(jù)不能被解析為 XML 或 HTML,該屬性等于null
。
該屬性生效的前提是 HTTP 回應(yīng)的Content-Type
頭信息等于text/xml
或application/xml
。這要求在發(fā)送請求前,XMLHttpRequest.responseType
屬性要設(shè)為document
。如果 HTTP 回應(yīng)的Content-Type
頭信息不等于text/xml
和application/xml
,但是想從responseXML
拿到數(shù)據(jù)(即把數(shù)據(jù)按照 DOM 格式解析),那么需要手動調(diào)用XMLHttpRequest.overrideMimeType()
方法,強(qiáng)制進(jìn)行 XML 解析。
該屬性得到的數(shù)據(jù),是直接解析后的文檔 DOM 樹。
var xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);
xhr.responseType = 'document';
xhr.overrideMimeType('text/xml');
xhr.onload = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseXML);
}
};
xhr.send(null);
XMLHttpRequest.responseURL
XMLHttpRequest.responseURL
屬性是字符串,表示發(fā)送數(shù)據(jù)的服務(wù)器的網(wǎng)址。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/test', true);
xhr.onload = function () {
// 返回 http://example.com/test
console.log(xhr.responseURL);
};
xhr.send(null);
注意,這個(gè)屬性的值與open()
方法指定的請求網(wǎng)址不一定相同。如果服務(wù)器端發(fā)生跳轉(zhuǎn),這個(gè)屬性返回最后實(shí)際返回?cái)?shù)據(jù)的網(wǎng)址。另外,如果原始 URL 包括錨點(diǎn)(fragment),該屬性會把錨點(diǎn)剝離。
XMLHttpRequest.status,XMLHttpRequest.statusText
XMLHttpRequest.status
屬性返回一個(gè)整數(shù),表示服務(wù)器回應(yīng)的 HTTP 狀態(tài)碼。一般來說,如果通信成功的話,這個(gè)狀態(tài)碼是200;如果服務(wù)器沒有返回狀態(tài)碼,那么這個(gè)屬性默認(rèn)是200。請求發(fā)出之前,該屬性為0
。該屬性只讀。
- 200, OK,訪問正常
- 301, Moved Permanently,永久移動
- 302, Moved temporarily,暫時(shí)移動
- 304, Not Modified,未修改
- 307, Temporary Redirect,暫時(shí)重定向
- 401, Unauthorized,未授權(quán)
- 403, Forbidden,禁止訪問
- 404, Not Found,未發(fā)現(xiàn)指定網(wǎng)址
- 500, Internal Server Error,服務(wù)器發(fā)生錯(cuò)誤
基本上,只有2xx和304的狀態(tài)碼,表示服務(wù)器返回是正常狀態(tài)。
if (xhr.readyState === 4) {
if ( (xhr.status >= 200 && xhr.status < 300)
|| (xhr.status === 304) ) {
// 處理服務(wù)器的返回?cái)?shù)據(jù)
} else {
// 出錯(cuò)
}
}
XMLHttpRequest.statusText
屬性返回一個(gè)字符串,表示服務(wù)器發(fā)送的狀態(tài)提示。不同于status
屬性,該屬性包含整個(gè)狀態(tài)信息,比如“OK”和“Not Found”。在請求發(fā)送之前(即調(diào)用open()
方法之前),該屬性的值是空字符串;如果服務(wù)器沒有返回狀態(tài)提示,該屬性的值默認(rèn)為“OK”。該屬性為只讀屬性。
XMLHttpRequest.timeout,XMLHttpRequestEventTarget.ontimeout
XMLHttpRequest.timeout
屬性返回一個(gè)整數(shù),表示多少毫秒后,如果請求仍然沒有得到結(jié)果,就會自動終止。如果該屬性等于0,就表示沒有時(shí)間限制。
XMLHttpRequestEventTarget.ontimeout
屬性用于設(shè)置一個(gè)監(jiān)聽函數(shù),如果發(fā)生 timeout 事件,就會執(zhí)行這個(gè)監(jiān)聽函數(shù)。
下面是一個(gè)例子。
var xhr = new XMLHttpRequest();
var url = '/server';
xhr.ontimeout = function () {
console.error('The request for ' + url + ' timed out.');
};
xhr.onload = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 處理服務(wù)器返回的數(shù)據(jù)
} else {
console.error(xhr.statusText);
}
}
};
xhr.open('GET', url, true);
// 指定 10 秒鐘超時(shí)
xhr.timeout = 10 * 1000;
xhr.send(null);
事件監(jiān)聽屬性
XMLHttpRequest 對象可以對以下事件指定監(jiān)聽函數(shù)。
- XMLHttpRequest.onloadstart:loadstart 事件(HTTP 請求發(fā)出)的監(jiān)聽函數(shù)
- XMLHttpRequest.onprogress:progress事件(正在發(fā)送和加載數(shù)據(jù))的監(jiān)聽函數(shù)
- XMLHttpRequest.onabort:abort 事件(請求中止,比如用戶調(diào)用了
abort()
方法)的監(jiān)聽函數(shù) - XMLHttpRequest.onerror:error 事件(請求失?。┑谋O(jiān)聽函數(shù)
- XMLHttpRequest.onload:load 事件(請求成功完成)的監(jiān)聽函數(shù)
- XMLHttpRequest.ontimeout:timeout 事件(用戶指定的時(shí)限超過了,請求還未完成)的監(jiān)聽函數(shù)
- XMLHttpRequest.onloadend:loadend 事件(請求完成,不管成功或失?。┑谋O(jiān)聽函數(shù)
下面是一個(gè)例子。
xhr.onload = function() {
var responseText = xhr.responseText;
console.log(responseText);
// process the response.
};
xhr.onabort = function () {
console.log('The request was aborted');
};
xhr.onprogress = function (event) {
console.log(event.loaded);
console.log(event.total);
};
xhr.onerror = function() {
console.log('There was an error!');
};
progress
事件的監(jiān)聽函數(shù)有一個(gè)事件對象參數(shù),該對象有三個(gè)屬性:loaded
屬性返回已經(jīng)傳輸?shù)臄?shù)據(jù)量,total
屬性返回總的數(shù)據(jù)量,lengthComputable
屬性返回一個(gè)布爾值,表示加載的進(jìn)度是否可以計(jì)算。所有這些監(jiān)聽函數(shù)里面,只有progress
事件的監(jiān)聽函數(shù)有參數(shù),其他函數(shù)都沒有參數(shù)。
注意,如果發(fā)生網(wǎng)絡(luò)錯(cuò)誤(比如服務(wù)器無法連通),onerror
事件無法獲取報(bào)錯(cuò)信息。也就是說,可能沒有錯(cuò)誤對象,所以這樣只能顯示報(bào)錯(cuò)的提示。
XMLHttpRequest.withCredentials
XMLHttpRequest.withCredentials
屬性是一個(gè)布爾值,表示跨域請求時(shí),用戶信息(比如 Cookie 和認(rèn)證的 HTTP 頭信息)是否會包含在請求之中,默認(rèn)為false
,即向example.com
發(fā)出跨域請求時(shí),不會發(fā)送example.com
設(shè)置在本機(jī)上的 Cookie(如果有的話)。
如果需要跨域 AJAX 請求發(fā)送 Cookie,需要withCredentials
屬性設(shè)為true
。注意,同源的請求不需要設(shè)置這個(gè)屬性。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);
為了讓這個(gè)屬性生效,服務(wù)器必須顯式返回Access-Control-Allow-Credentials
這個(gè)頭信息。
Access-Control-Allow-Credentials: true
withCredentials
屬性打開的話,跨域請求不僅會發(fā)送 Cookie,還會設(shè)置遠(yuǎn)程主機(jī)指定的 Cookie。反之也成立,如果withCredentials
屬性沒有打開,那么跨域的 AJAX 請求即使明確要求瀏覽器設(shè)置 Cookie,瀏覽器也會忽略。
注意,腳本總是遵守同源政策,無法從document.cookie
或者 HTTP 回應(yīng)的頭信息之中,讀取跨域的 Cookie,withCredentials
屬性不影響這一點(diǎn)。
XMLHttpRequest.upload
XMLHttpRequest 不僅可以發(fā)送請求,還可以發(fā)送文件,這就是 AJAX 文件上傳。發(fā)送文件以后,通過XMLHttpRequest.upload
屬性可以得到一個(gè)對象,通過觀察這個(gè)對象,可以得知上傳的進(jìn)展。主要方法就是監(jiān)聽這個(gè)對象的各種事件:loadstart、loadend、load、abort、error、progress、timeout。
假定網(wǎng)頁上有一個(gè)<progress>
元素。
<progress min="0" max="100" value="0">0% complete</progress>
文件上傳時(shí),對upload
屬性指定progress
事件的監(jiān)聽函數(shù),即可獲得上傳的進(jìn)度。
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function (e) {};
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
progressBar.value = (e.loaded / e.total) * 100;
// 兼容不支持 <progress> 元素的老式瀏覽器
progressBar.textContent = progressBar.value;
}
};
xhr.send(blobOrFile);
}
upload(new Blob(['hello world'], {type: 'text/plain'}));
XMLHttpRequest 的實(shí)例方法
XMLHttpRequest.open()
XMLHttpRequest.open()
方法用于指定 HTTP 請求的參數(shù),或者說初始化 XMLHttpRequest 實(shí)例對象。它一共可以接受五個(gè)參數(shù)。
void open(
string method,
string url,
optional boolean async,
optional string user,
optional string password
);
method
:表示 HTTP 動詞方法,比如GET
、POST
、PUT
、DELETE
、HEAD
等。url
: 表示請求發(fā)送目標(biāo) URL。async
: 布爾值,表示請求是否為異步,默認(rèn)為true
。如果設(shè)為false
,則send()
方法只有等到收到服務(wù)器返回了結(jié)果,才會進(jìn)行下一步操作。該參數(shù)可選。由于同步 AJAX 請求會造成瀏覽器失去響應(yīng),許多瀏覽器已經(jīng)禁止在主線程使用,只允許 Worker 里面使用。所以,這個(gè)參數(shù)輕易不應(yīng)該設(shè)為false
。user
:表示用于認(rèn)證的用戶名,默認(rèn)為空字符串。該參數(shù)可選。password
:表示用于認(rèn)證的密碼,默認(rèn)為空字符串。該參數(shù)可選。
注意,如果對使用過open()
方法的 AJAX 請求,再次使用這個(gè)方法,等同于調(diào)用abort()
,即終止請求。
下面發(fā)送 POST 請求的例子。
var xhr = new XMLHttpRequest();
xhr.open('POST', encodeURI('someURL'));
XMLHttpRequest.send()
XMLHttpRequest.send()
方法用于實(shí)際發(fā)出 HTTP 請求。它的參數(shù)是可選的,如果不帶參數(shù),就表示 HTTP 請求只有一個(gè) URL,沒有數(shù)據(jù)體,典型例子就是 GET 請求;如果帶有參數(shù),就表示除了頭信息,還帶有包含具體數(shù)據(jù)的信息體,典型例子就是 POST 請求。
下面是 GET 請求的例子。
var xhr = new XMLHttpRequest();
xhr.open('GET',
'http://www.example.com/?id=' + encodeURIComponent(id),
true
);
xhr.send(null);
上面代碼中,GET
請求的參數(shù),作為查詢字符串附加在 URL 后面。
下面是發(fā)送 POST 請求的例子。
var xhr = new XMLHttpRequest();
var data = 'email='
+ encodeURIComponent(email)
+ '&password='
+ encodeURIComponent(password);
xhr.open('POST', 'http://www.example.com', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(data);
注意,所有 XMLHttpRequest 的監(jiān)聽事件,都必須在send()
方法調(diào)用之前設(shè)定。
send
方法的參數(shù)就是發(fā)送的數(shù)據(jù)。多種格式的數(shù)據(jù),都可以作為它的參數(shù)。
void send();
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(String data);
void send(FormData data);
如果send()
發(fā)送 DOM 對象,在發(fā)送之前,數(shù)據(jù)會先被串行化。如果發(fā)送二進(jìn)制數(shù)據(jù),最好是發(fā)送ArrayBufferView
或Blob
對象,這使得通過 Ajax 上傳文件成為可能。
下面是發(fā)送表單數(shù)據(jù)的例子。FormData
對象可以用于構(gòu)造表單數(shù)據(jù)。
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
對象構(gòu)造了表單數(shù)據(jù),然后使用send()
方法發(fā)送。它的效果與發(fā)送下面的表單數(shù)據(jù)是一樣的。
<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
對象加工表單數(shù)據(jù),然后再發(fā)送。
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() {
// ...
};
xhr.send(formData);
return false;
}
var form = document.querySelector('#registration');
sendForm(form);
XMLHttpRequest.setRequestHeader()
XMLHttpRequest.setRequestHeader()
方法用于設(shè)置瀏覽器發(fā)送的 HTTP 請求的頭信息。該方法必須在open()
之后、send()
之前調(diào)用。如果該方法多次調(diào)用,設(shè)定同一個(gè)字段,則每一次調(diào)用的值會被合并成一個(gè)單一的值發(fā)送。
該方法接受兩個(gè)參數(shù)。第一個(gè)參數(shù)是字符串,表示頭信息的字段名,第二個(gè)參數(shù)是字段值。
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
xhr.send(JSON.stringify(data));
上面代碼首先設(shè)置頭信息Content-Type
,表示發(fā)送 JSON 格式的數(shù)據(jù);然后設(shè)置Content-Length
,表示數(shù)據(jù)長度;最后發(fā)送 JSON 數(shù)據(jù)。
XMLHttpRequest.overrideMimeType()
XMLHttpRequest.overrideMimeType()
方法用來指定 MIME 類型,覆蓋服務(wù)器返回的真正的 MIME 類型,從而讓瀏覽器進(jìn)行不一樣的處理。舉例來說,服務(wù)器返回的數(shù)據(jù)類型是text/xml
,由于種種原因?yàn)g覽器解析不成功報(bào)錯(cuò),這時(shí)就拿不到數(shù)據(jù)了。為了拿到原始數(shù)據(jù),我們可以把 MIME 類型改成text/plain
,這樣瀏覽器就不會去自動解析,從而我們就可以拿到原始文本了。
xhr.overrideMimeType('text/plain')
注意,該方法必須在send()
方法之前調(diào)用。
修改服務(wù)器返回的數(shù)據(jù)類型,不是正常情況下應(yīng)該采取的方法。如果希望服務(wù)器返回指定的數(shù)據(jù)類型,可以用responseType
屬性告訴服務(wù)器,就像下面的例子。只有在服務(wù)器無法返回某種數(shù)據(jù)類型時(shí),才使用overrideMimeType()
方法。
var xhr = new XMLHttpRequest();
xhr.onload = function(e) {
var arraybuffer = xhr.response;
// ...
}
xhr.open('GET', url);
xhr.responseType = 'arraybuffer';
xhr.send();
XMLHttpRequest.getResponseHeader()
XMLHttpRequest.getResponseHeader()
方法返回 HTTP 頭信息指定字段的值,如果還沒有收到服務(wù)器回應(yīng)或者指定字段不存在,返回null
。該方法的參數(shù)不區(qū)分大小寫。
function getHeaderTime() {
console.log(this.getResponseHeader("Last-Modified"));
}
var xhr = new XMLHttpRequest();
xhr.open('HEAD', 'yourpage.html');
xhr.onload = getHeaderTime;
xhr.send();
如果有多個(gè)字段同名,它們的值會被連接為一個(gè)字符串,每個(gè)字段之間使用“逗號+空格”分隔。
XMLHttpRequest.getAllResponseHeaders()
XMLHttpRequest.getAllResponseHeaders()
方法返回一個(gè)字符串,表示服務(wù)器發(fā)來的所有 HTTP 頭信息。格式為字符串,每個(gè)頭信息之間使用CRLF
分隔(回車+換行),如果沒有收到服務(wù)器回應(yīng),該屬性為null
。如果發(fā)生網(wǎng)絡(luò)錯(cuò)誤,該屬性為空字符串。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'foo.txt', true);
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
var headers = xhr.getAllResponseHeaders();
}
}
上面代碼用于獲取服務(wù)器返回的所有頭信息。它可能是下面這樣的字符串。
date: Fri, 08 Dec 2017 21:04:30 GMT\r\n
content-encoding: gzip\r\n
x-content-type-options: nosniff\r\n
server: meinheld/0.6.1\r\n
x-frame-options: DENY\r\n
content-type: text/html; charset=utf-8\r\n
connection: keep-alive\r\n
strict-transport-security: max-age=63072000\r\n
vary: Cookie, Accept-Encoding\r\n
content-length: 6502\r\n
x-xss-protection: 1; mode=block\r\n
然后,對這個(gè)字符串進(jìn)行處理。
var arr = headers.trim().split(/[\r\n]+/);
var headerMap = {};
arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift();
var value = parts.join(': ');
headerMap[header] = value;
});
headerMap['content-length'] // "6502"
XMLHttpRequest.abort()
XMLHttpRequest.abort()
方法用來終止已經(jīng)發(fā)出的 HTTP 請求。調(diào)用這個(gè)方法以后,readyState
屬性變?yōu)?code>4,status
屬性變?yōu)?code>0。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.example.com/page.php', true);
setTimeout(function () {
if (xhr) {
xhr.abort();
xhr = null;
}
}, 5000);
上面代碼在發(fā)出5秒之后,終止一個(gè) AJAX 請求。
XMLHttpRequest 實(shí)例的事件
readyStateChange 事件
readyState
屬性的值發(fā)生改變,就會觸發(fā) readyStateChange 事件。
我們可以通過onReadyStateChange
屬性,指定這個(gè)事件的監(jiān)聽函數(shù),對不同狀態(tài)進(jìn)行不同處理。尤其是當(dāng)狀態(tài)變?yōu)?code>4的時(shí)候,表示通信成功,這時(shí)回調(diào)函數(shù)就可以處理服務(wù)器傳送回來的數(shù)據(jù)。
progress 事件
上傳文件時(shí),XMLHttpRequest 實(shí)例對象本身和實(shí)例的upload
屬性,都有一個(gè)progress
事件,會不斷返回上傳的進(jìn)度。
var xhr = new XMLHttpRequest();
function updateProgress (oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total;
} else {
console.log('無法計(jì)算進(jìn)展');
}
}
xhr.addEventListener('progress', updateProgress);
xhr.open();
load 事件、error 事件、abort 事件
load 事件表示服務(wù)器傳來的數(shù)據(jù)接收完畢,error 事件表示請求出錯(cuò),abort 事件表示請求被中斷(比如用戶取消請求)。
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', transferComplete);
xhr.addEventListener('error', transferFailed);
xhr.addEventListener('abort', transferCanceled);
xhr.open();
function transferComplete() {
console.log('數(shù)據(jù)接收完畢');
}
function transferFailed() {
console.log('數(shù)據(jù)接收出錯(cuò)');
}
function transferCanceled() {
console.log('用戶取消接收');
}
loadend 事件
abort
、load
和error
這三個(gè)事件,會伴隨一個(gè)loadend
事件,表示請求結(jié)束,但不知道其是否成功。
xhr.addEventListener('loadend', loadEnd);
function loadEnd(e) {
console.log('請求結(jié)束,狀態(tài)未知');
}
timeout 事件
服務(wù)器超過指定時(shí)間還沒有返回結(jié)果,就會觸發(fā) timeout 事件,具體的例子參見timeout
屬性一節(jié)。
Navigator.sendBeacon()
用戶卸載網(wǎng)頁的時(shí)候,有時(shí)需要向服務(wù)器發(fā)一些數(shù)據(jù)。很自然的做法是在unload
事件或beforeunload
事件的監(jiān)聽函數(shù)里面,使用XMLHttpRequest
對象發(fā)送數(shù)據(jù)。但是,這樣做不是很可靠,因?yàn)?code>XMLHttpRequest對象是異步發(fā)送,很可能在它即將發(fā)送的時(shí)候,頁面已經(jīng)卸載了,從而導(dǎo)致發(fā)送取消或者發(fā)送失敗。
解決方法就是unload
事件里面,加一些很耗時(shí)的同步操作。這樣就能留出足夠的時(shí)間,保證異步 AJAX 能夠發(fā)送成功。
function log() {
let xhr = new XMLHttpRequest();
xhr.open('post', '/log', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('foo=bar');
}
window.addEventListener('unload', function(event) {
log();
// a time-consuming operation
for (let i = 1; i < 10000; i++) {
for (let m = 1; m < 10000; m++) { continue; }
}
});
上面代碼中,強(qiáng)制執(zhí)行了一次雙重循環(huán),拖長了unload
事件的執(zhí)行時(shí)間,導(dǎo)致異步 AJAX 能夠發(fā)送成功。
類似的還可以使用setTimeout()
。下面是追蹤用戶點(diǎn)擊的例子。
// HTML 代碼如下
// <a id="target" rel="external nofollow" target="_blank" >click</a>
const clickTime = 350;
const theLink = document.getElementById('target');
function log() {
let xhr = new XMLHttpRequest();
xhr.open('post', '/log', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('foo=bar');
}
theLink.addEventListener('click', function (event) {
event.preventDefault();
log();
setTimeout(function () {
window.location.href = theLink.getAttribute('href');
}, clickTime);
});
上面代碼使用setTimeout()
,拖延了350毫秒,才讓頁面跳轉(zhuǎn),因此使得異步 AJAX 有時(shí)間發(fā)出。
這些做法的共同問題是,卸載的時(shí)間被硬生生拖長了,后面頁面的加載被推遲了,用戶體驗(yàn)不好。
為了解決這個(gè)問題,瀏覽器引入了Navigator.sendBeacon()
方法。這個(gè)方法還是異步發(fā)出請求,但是請求與當(dāng)前頁面線程脫鉤,作為瀏覽器進(jìn)程的任務(wù),因此可以保證會把數(shù)據(jù)發(fā)出去,不拖延卸載流程。
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon('/log', JSON.stringify({
some: "data"
}));
}
Navigator.sendBeacon()
方法接受兩個(gè)參數(shù),第一個(gè)參數(shù)是目標(biāo)服務(wù)器的 URL,第二個(gè)參數(shù)是所要發(fā)送的數(shù)據(jù)(可選),可以是任意類型(字符串、表單對象、二進(jìn)制對象等等)。
navigator.sendBeacon(url, data)
這個(gè)方法的返回值是一個(gè)布爾值,成功發(fā)送數(shù)據(jù)為true
,否則為false
。
該方法發(fā)送數(shù)據(jù)的 HTTP 方法是 POST,可以跨域,類似于表單提交數(shù)據(jù)。它不能指定回調(diào)函數(shù)。
下面是一個(gè)例子。
// HTML 代碼如下
// <body onload="analytics('start')" onunload="analytics('end')">
function analytics(state) {
if (!navigator.sendBeacon) return;
var URL = 'http://example.com/analytics';
var data = 'state=' + state + '&location=' + window.location;
navigator.sendBeacon(URL, data);
}
該方法不允許自定義 HTTP 標(biāo)頭,為了以“application/json”的形式發(fā)送數(shù)據(jù),可以使用 Blob 對象。
const blob = new Blob(
[ JSON.stringify({ some: "data" }) ],
{ type: 'application/json; charset=UTF-8' }
);
navigator.sendBeacon('/log', blob));
這個(gè)方法的優(yōu)先級較低,不會占用頁面資源。一般是在瀏覽器空閑的時(shí)候,才會發(fā)送。
更多建議: