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