W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
?XMLHttpRequest
? 是一個(gè)內(nèi)建的瀏覽器對(duì)象,它允許使用 JavaScript 發(fā)送 HTTP 請(qǐng)求。
雖然它的名字里面有 “XML” 一詞,但它可以操作任何數(shù)據(jù),而不僅僅是 XML 格式。我們可以用它來(lái)上傳/下載文件,跟蹤進(jìn)度等。
現(xiàn)如今,我們有一個(gè)更為現(xiàn)代的方法叫做 fetch
,它的出現(xiàn)使得 XMLHttpRequest
在某種程度上被棄用。
在現(xiàn)代 Web 開(kāi)發(fā)中,出于以下三種原因,我們還在使用 XMLHttpRequest
:
XMLHttpRequest
? 的腳本。fetch
? 目前無(wú)法做到的事情,例如跟蹤上傳進(jìn)度。這些話聽(tīng)起來(lái)熟悉嗎?如果是,那么請(qǐng)繼續(xù)閱讀下面的 XMLHttpRequest
相關(guān)內(nèi)容吧。如果還不是很熟悉的話,那么請(qǐng)先閱讀 Fetch 一章的內(nèi)容。
XMLHttpRequest 有兩種執(zhí)行模式:同步(synchronous)和異步(asynchronous)。
我們首先來(lái)看看最常用的異步模式:
要發(fā)送請(qǐng)求,需要 3 個(gè)步驟:
XMLHttpRequest
:let xhr = new XMLHttpRequest();
此構(gòu)造器沒(méi)有參數(shù)。
new XMLHttpRequest
之后:xhr.open(method, URL, [async, user, password])
此方法指定請(qǐng)求的主要參數(shù):
method
? —— HTTP 方法。通常是 ?"GET"
? 或 ?"POST"
?。URL
? —— 要請(qǐng)求的 URL,通常是一個(gè)字符串,也可以是 URL 對(duì)象。async
? —— 如果顯式地設(shè)置為 ?false
?,那么請(qǐng)求將會(huì)以同步的方式處理,我們稍后會(huì)講到它。user
?,?password
? —— HTTP 基本身份驗(yàn)證(如果需要的話)的登錄名和密碼。請(qǐng)注意,open
調(diào)用與其名稱相反,不會(huì)建立連接。它僅配置請(qǐng)求,而網(wǎng)絡(luò)活動(dòng)僅以 send
調(diào)用開(kāi)啟。
xhr.send([body])
這個(gè)方法會(huì)建立連接,并將請(qǐng)求發(fā)送到服務(wù)器??蛇x參數(shù) body
包含了 request body。
一些請(qǐng)求方法,像 GET
沒(méi)有 request body。還有一些請(qǐng)求方法,像 POST
使用 body
將數(shù)據(jù)發(fā)送到服務(wù)器。我們稍后會(huì)看到相應(yīng)示例。
xhr
事件以獲取響應(yīng)。這三個(gè)事件是最常用的:
load
? —— 當(dāng)請(qǐng)求完成(即使 HTTP 狀態(tài)為 400 或 500 等),并且響應(yīng)已完全下載。error
? —— 當(dāng)無(wú)法發(fā)出請(qǐng)求,例如網(wǎng)絡(luò)中斷或者無(wú)效的 URL。progress
? —— 在下載響應(yīng)期間定期觸發(fā),報(bào)告已經(jīng)下載了多少。xhr.onload = function() {
alert(`Loaded: ${xhr.status} ${xhr.response}`);
};
xhr.onerror = function() { // 僅在根本無(wú)法發(fā)出請(qǐng)求時(shí)觸發(fā)
alert(`Network Error`);
};
xhr.onprogress = function(event) { // 定期觸發(fā)
// event.loaded —— 已經(jīng)下載了多少字節(jié)
// event.lengthComputable = true,當(dāng)服務(wù)器發(fā)送了 Content-Length header 時(shí)
// event.total —— 總字節(jié)數(shù)(如果 lengthComputable 為 true)
alert(`Received ${event.loaded} of ${event.total}`);
};
下面是一個(gè)完整的示例。它從服務(wù)器加載 /article/xmlhttprequest/example/load
,并打印加載進(jìn)度:
// 1. 創(chuàng)建一個(gè) new XMLHttpRequest 對(duì)象
let xhr = new XMLHttpRequest();
// 2. 配置它:從 URL /article/.../load GET-request
xhr.open('GET', '/article/xmlhttprequest/example/load');
// 3. 通過(guò)網(wǎng)絡(luò)發(fā)送請(qǐng)求
xhr.send();
// 4. 當(dāng)接收到響應(yīng)后,將調(diào)用此函數(shù)
xhr.onload = function() {
if (xhr.status != 200) { // 分析響應(yīng)的 HTTP 狀態(tài)
alert(`Error ${xhr.status}: ${xhr.statusText}`); // 例如 404: Not Found
} else { // 顯示結(jié)果
alert(`Done, got ${xhr.response.length} bytes`); // response 是服務(wù)器響應(yīng)
}
};
xhr.onprogress = function(event) {
if (event.lengthComputable) {
alert(`Received ${event.loaded} of ${event.total} bytes`);
} else {
alert(`Received ${event.loaded} bytes`); // 沒(méi)有 Content-Length
}
};
xhr.onerror = function() {
alert("Request failed");
};
一旦服務(wù)器有了響應(yīng),我們可以在以下 xhr
屬性中接收結(jié)果:
?status
?
HTTP 狀態(tài)碼(一個(gè)數(shù)字):?200
?,?404
?,?403
? 等,如果出現(xiàn)非 HTTP 錯(cuò)誤,則為 ?0
?。
?statusText
?
HTTP 狀態(tài)消息(一個(gè)字符串):狀態(tài)碼為 ?200
? 對(duì)應(yīng)于 ?OK
?,?404
? 對(duì)應(yīng)于 ?Not Found
?,?403
? 對(duì)應(yīng)于 ?Forbidden
?。
?response
?(舊腳本可能用的是 ?responseText
?)
服務(wù)器 response body。
我們還可以使用相應(yīng)的屬性指定超時(shí)(timeout):
xhr.timeout = 10000; // timeout 單位是 ms,此處即 10 秒
如果在給定時(shí)間內(nèi)請(qǐng)求沒(méi)有成功執(zhí)行,請(qǐng)求就會(huì)被取消,并且觸發(fā) timeout
事件。
URL 搜索參數(shù)(URL search parameters)
為了向 URL 添加像
?name=value
這樣的參數(shù),并確保正確的編碼,我們可以使用 URL 對(duì)象:
let url = new URL('https://google.com/search'); url.searchParams.set('q', 'test me!'); // 參數(shù) 'q' 被編碼 xhr.open('GET', url); // https://google.com/search?q=test+me%21
我們可以使用 xhr.responseType
屬性來(lái)設(shè)置響應(yīng)格式:
""
?(默認(rèn))—— 響應(yīng)格式為字符串,"text"
? —— 響應(yīng)格式為字符串,"arraybuffer"
? —— 響應(yīng)格式為 ?ArrayBuffer
?(對(duì)于二進(jìn)制數(shù)據(jù),請(qǐng)參見(jiàn) ArrayBuffer,二進(jìn)制數(shù)組),"blob"
? —— 響應(yīng)格式為 ?Blob
?(對(duì)于二進(jìn)制數(shù)據(jù),請(qǐng)參見(jiàn) Blob),"document"
? —— 響應(yīng)格式為 XML document(可以使用 XPath 和其他 XML 方法)或 HTML document(基于接收數(shù)據(jù)的 MIME 類型)"json"
? —— 響應(yīng)格式為 JSON(自動(dòng)解析)。例如,我們以 JSON 格式獲取響應(yīng):
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/example/json');
xhr.responseType = 'json';
xhr.send();
// 響應(yīng)為 {"message": "Hello, world!"}
xhr.onload = function() {
let responseObj = xhr.response;
alert(responseObj.message); // Hello, world!
};
請(qǐng)注意:在舊的腳本中,你可能會(huì)看到
xhr.responseText
,甚至?xí)吹?nbsp;xhr.responseXML
屬性。
它們是由于歷史原因而存在的,以獲取字符串或 XML 文檔。如今,我們應(yīng)該在
xhr.responseType
中設(shè)置格式,然后就能獲取如上所示的xhr.response
了。
XMLHttpRequest
的狀態(tài)(state)會(huì)隨著它的處理進(jìn)度變化而變化??梢酝ㄟ^(guò) xhr.readyState
來(lái)了解當(dāng)前狀態(tài)。
規(guī)范 中提到的所有狀態(tài)如下:
UNSENT = 0; // 初始狀態(tài)
OPENED = 1; // open 被調(diào)用
HEADERS_RECEIVED = 2; // 接收到 response header
LOADING = 3; // 響應(yīng)正在被加載(接收到一個(gè)數(shù)據(jù)包)
DONE = 4; // 請(qǐng)求完成
XMLHttpRequest
對(duì)象以 0
→ 1
→ 2
→ 3
→ … → 3
→ 4
的順序在它們之間轉(zhuǎn)變。每當(dāng)通過(guò)網(wǎng)絡(luò)接收到一個(gè)數(shù)據(jù)包,就會(huì)重復(fù)一次狀態(tài) 3
。
我們可以使用 readystatechange
事件來(lái)跟蹤它們:
xhr.onreadystatechange = function() {
if (xhr.readyState == 3) {
// 加載中
}
if (xhr.readyState == 4) {
// 請(qǐng)求完成
}
};
你可能在非常老的代碼中找到 readystatechange
這樣的事件監(jiān)聽(tīng)器,它的存在是有歷史原因的,因?yàn)樵?jīng)有很長(zhǎng)一段時(shí)間都沒(méi)有 load
以及其他事件。如今,它已被 load/error/progress
事件處理程序所替代。
我們可以隨時(shí)終止請(qǐng)求。調(diào)用 ?xhr.abort()
? 即可:
xhr.abort(); // 終止請(qǐng)求
它會(huì)觸發(fā) abort
事件,且 xhr.status
變?yōu)?nbsp;0
。
如果在 open
方法中將第三個(gè)參數(shù) async
設(shè)置為 false
,那么請(qǐng)求就會(huì)以同步的方式進(jìn)行。
換句話說(shuō),JavaScript 執(zhí)行在 send()
處暫停,并在收到響應(yīng)后恢復(fù)執(zhí)行。這有點(diǎn)兒像 alert
或 prompt
命令。
下面是重寫(xiě)的示例,open
的第三個(gè)參數(shù)為 false
:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/hello.txt', false);
try {
xhr.send();
if (xhr.status != 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else {
alert(xhr.response);
}
} catch(err) { // 代替 onerror
alert("Request failed");
}
這看起來(lái)好像不錯(cuò),但是很少使用同步調(diào)用,因?yàn)樗鼈儠?huì)阻塞頁(yè)面內(nèi)的 JavaScript,直到加載完成。在某些瀏覽器中,滾動(dòng)可能無(wú)法正常進(jìn)行。如果一個(gè)同步調(diào)用執(zhí)行時(shí)間過(guò)長(zhǎng),瀏覽器可能會(huì)建議關(guān)閉“掛起(hanging)”的網(wǎng)頁(yè)。
XMLHttpRequest
的很多高級(jí)功能在同步請(qǐng)求中都不可用,例如向其他域發(fā)起請(qǐng)求或者設(shè)置超時(shí)。并且,正如你所看到的,沒(méi)有進(jìn)度指示。
基于這些原因,同步請(qǐng)求使用的非常少,幾乎從不使用。在這我們就不再討論它了。
XMLHttpRequest
允許發(fā)送自定義 header,并且可以從響應(yīng)中讀取 header。
HTTP-header 有三種方法:
?setRequestHeader(name, value)
?
使用給定的 name
和 value
設(shè)置 request header。
例如:
xhr.setRequestHeader('Content-Type', 'application/json');
Header 的限制
一些 header 是由瀏覽器專門(mén)管理的,例如
Referer
和Host
。 完整列表請(qǐng)見(jiàn) 規(guī)范。
為了用戶安全和請(qǐng)求的正確性,
XMLHttpRequest
不允許更改它們。
不能移除 header
XMLHttpRequest
的另一個(gè)特點(diǎn)是不能撤銷setRequestHeader
。
一旦設(shè)置了 header,就無(wú)法撤銷了。其他調(diào)用會(huì)向 header 中添加信息,但不會(huì)覆蓋它。
例如:
xhr.setRequestHeader('X-Auth', '123'); xhr.setRequestHeader('X-Auth', '456'); // header 將是: // X-Auth: 123, 456
?getResponseHeader(name)
?
獲取具有給定 name
的 header(Set-Cookie
和 Set-Cookie2
除外)。
例如:
xhr.getResponseHeader('Content-Type')
?getAllResponseHeaders()
?
返回除 Set-Cookie
和 Set-Cookie2
外的所有 response header。
header 以單行形式返回,例如:
Cache-Control: max-age=31536000
Content-Length: 4260
Content-Type: image/png
Date: Sat, 08 Sep 2012 16:53:16 GMT
header 之間的換行符始終為 "\r\n"
(不依賴于操作系統(tǒng)),所以我們可以很容易地將其拆分為單獨(dú)的 header。name 和 value 之間總是以冒號(hào)后跟一個(gè)空格 ": "
分隔。這是標(biāo)準(zhǔn)格式。
因此,如果我們想要獲取具有 name/value 對(duì)的對(duì)象,則需要用一點(diǎn) JavaScript 代碼來(lái)處理它們。
像這樣(假設(shè)如果兩個(gè) header 具有相同的名稱,那么后者就會(huì)覆蓋前者):
let headers = xhr
.getAllResponseHeaders()
.split('\r\n')
.reduce((result, current) => {
let [name, value] = current.split(': ');
result[name] = value;
return result;
}, {});
// headers['Content-Type'] = 'image/png'
要建立一個(gè) POST 請(qǐng)求,我們可以使用內(nèi)建的 FormData 對(duì)象。
語(yǔ)法為:
let formData = new FormData([form]); // 創(chuàng)建一個(gè)對(duì)象,可以選擇從 <form> 中獲取數(shù)據(jù)
formData.append(name, value); // 附加一個(gè)字段
我們創(chuàng)建它,可以選擇從一個(gè)表單中獲取數(shù)據(jù),如果需要,還可以 append
更多字段,然后:
xhr.open('POST', ...)
? —— 使用 ?POST
? 方法。xhr.send(formData)
? 將表單發(fā)送到服務(wù)器。例如:
<form name="person">
<input name="name" value="John">
<input name="surname" value="Smith">
</form>
<script>
// 從表單預(yù)填充 FormData
let formData = new FormData(document.forms.person);
// 附加一個(gè)字段
formData.append("middle", "Lee");
// 將其發(fā)送出去
let xhr = new XMLHttpRequest();
xhr.open("POST", "/article/xmlhttprequest/post/user");
xhr.send(formData);
xhr.onload = () => alert(xhr.response);
</script>
以 multipart/form-data
編碼發(fā)送表單。
或者,如果我們更喜歡 JSON,那么可以使用 JSON.stringify
并以字符串形式發(fā)送。
只是,不要忘記設(shè)置 header Content-Type: application/json
,只要有了它,很多服務(wù)端框架都能自動(dòng)解碼 JSON:
let xhr = new XMLHttpRequest();
let json = JSON.stringify({
name: "John",
surname: "Smith"
});
xhr.open("POST", '/submit')
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.send(json);
.send(body)
方法就像一個(gè)非常雜食性的動(dòng)物。它幾乎可以發(fā)送任何 body
,包括 Blob
和 BufferSource
對(duì)象。
?progress
? 事件僅在下載階段觸發(fā)。
也就是說(shuō):如果我們 POST
一些內(nèi)容,XMLHttpRequest
首先上傳我們的數(shù)據(jù)(request body),然后下載響應(yīng)。
如果我們要上傳的東西很大,那么我們肯定會(huì)對(duì)跟蹤上傳進(jìn)度感興趣。但是 xhr.onprogress
在這里并不起作用。
這里有另一個(gè)對(duì)象,它沒(méi)有方法,它專門(mén)用于跟蹤上傳事件:xhr.upload
。
它會(huì)生成事件,類似于 xhr
,但是 xhr.upload
僅在上傳時(shí)觸發(fā)它們:
loadstart
? —— 上傳開(kāi)始。progress
? —— 上傳期間定期觸發(fā)。abort
? —— 上傳中止。error
? —— 非 HTTP 錯(cuò)誤。load
? —— 上傳成功完成。timeout
? —— 上傳超時(shí)(如果設(shè)置了 ?timeout
? 屬性)。loadend
? —— 上傳完成,無(wú)論成功還是 error。handler 示例:
xhr.upload.onprogress = function(event) {
alert(`Uploaded ${event.loaded} of ${event.total} bytes`);
};
xhr.upload.onload = function() {
alert(`Upload finished successfully.`);
};
xhr.upload.onerror = function() {
alert(`Error during the upload: ${xhr.status}`);
};
這是一個(gè)真實(shí)示例:帶有進(jìn)度指示的文件上傳:
<input type="file" onchange="upload(this.files[0])">
<script>
function upload(file) {
let xhr = new XMLHttpRequest();
// 跟蹤上傳進(jìn)度
xhr.upload.onprogress = function(event) {
console.log(`Uploaded ${event.loaded} of ${event.total}`);
};
// 跟蹤完成:無(wú)論成功與否
xhr.onloadend = function() {
if (xhr.status == 200) {
console.log("success");
} else {
console.log("error " + this.status);
}
};
xhr.open("POST", "/article/xmlhttprequest/post/upload");
xhr.send(file);
}
</script>
XMLHttpRequest
可以使用和 fetch 相同的 CORS 策略進(jìn)行跨源請(qǐng)求。
就像 fetch
一樣,默認(rèn)情況下不會(huì)將 cookie 和 HTTP 授權(quán)發(fā)送到其他域。要啟用它們,可以將 xhr.withCredentials
設(shè)置為 true
:
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'http://anywhere.com/request');
...
有關(guān)跨源 header 的詳細(xì)信息,請(qǐng)見(jiàn) Fetch:跨源請(qǐng)求 一章。
使用 XMLHttpRequest
的 GET 請(qǐng)求的典型代碼:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/my/url');
xhr.send();
xhr.onload = function() {
if (xhr.status != 200) { // HTTP error?
// 處理 error
alert( 'Error: ' + xhr.status);
return;
}
// 獲取來(lái)自 xhr.response 的響應(yīng)
};
xhr.onprogress = function(event) {
// 報(bào)告進(jìn)度
alert(`Loaded ${event.loaded} of ${event.total}`);
};
xhr.onerror = function() {
// 處理非 HTTP error(例如網(wǎng)絡(luò)中斷)
};
實(shí)際上還有很多事件,在 現(xiàn)代規(guī)范 中有詳細(xì)列表(按生命周期排序):
loadstart
? —— 請(qǐng)求開(kāi)始。progress
? —— 一個(gè)響應(yīng)數(shù)據(jù)包到達(dá),此時(shí)整個(gè) response body 都在 ?response
? 中。abort
? —— 調(diào)用 ?xhr.abort()
? 取消了請(qǐng)求。load
?,?error
?,?timeout
? 或 ?abort
? 之后觸發(fā)。error
,abort
,timeout
和 load
事件是互斥的。其中只有一種可能發(fā)生。
最常用的事件是加載完成(load
),加載失敗(error
),或者我們可以使用單個(gè) loadend
處理程序并檢查請(qǐng)求對(duì)象 xhr
的屬性,以查看發(fā)生了什么。
我們還了解了另一個(gè)事件:readystatechange
。由于歷史原因,它早在規(guī)范制定之前就出現(xiàn)了。如今我們已經(jīng)無(wú)需使用它了,我們可以用新的事件代替它,但通常可以在舊的代碼中找到它。
如果我們需要專門(mén)跟蹤上傳,那么我們應(yīng)該在 xhr.upload
對(duì)象上監(jiān)聽(tīng)相同的事件。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: