JS XMLHttpRequest原理與使用方法深入詳解
本文實(shí)例講述了JS XMLHttpRequest原理與使用方法。分享給大家供大家參考,具體如下:
你真的會(huì)使用XMLHttpRequest嗎?
看到標(biāo)題時(shí),有些同學(xué)可能會(huì)想:“我已經(jīng)用
xhr
成功地發(fā)過(guò)很多個(gè)Ajax
請(qǐng)求了,對(duì)它的基本操作已經(jīng)算挺熟練了?!?我之前的想法和你們一樣,直到最近我使用xhr
時(shí)踩了不少坑兒,我才突然發(fā)現(xiàn)其實(shí)自己并不夠了解xhr
,我知道的只是最最基本的使用。
于是我決定好好地研究一番xhr
的真面目,可拜讀了不少博客后都不甚滿意,于是我決定認(rèn)真閱讀一遍W3C的XMLHttpRequest
標(biāo)準(zhǔn)??赐陿?biāo)準(zhǔn)后我如同醍醐灌頂一般,感覺到了從未有過(guò)的清澈。這篇文章就是參考W3C的XMLHttpRequest
標(biāo)準(zhǔn)和結(jié)合一些實(shí)踐驗(yàn)證總結(jié)而來(lái)的。
Ajax和XMLHttpRequest
我們通常將Ajax
等同于XMLHttpRequest
,但細(xì)究起來(lái)它們兩個(gè)是屬于不同維度的2個(gè)概念。
以下是我認(rèn)為對(duì)
Ajax
較為準(zhǔn)確的解釋:(摘自what is Ajax)
AJAX stands for Asynchronous JavaScript and XML. AJAX is a new technique for creating better, faster, and more interactive web applications with the help of XML, HTML, CSS, and Java Script.AJAX is based on the following open standards:
Browser-based presentation using HTML and Cascading Style Sheets (CSS).
Data is stored in XML format and fetched from the server.
Behind-the-scenes data fetches using XMLHttpRequest objects in the browser.
JavaScript to make everything happen.
從上面的解釋中可以知道:ajax
是一種技術(shù)方案,但并不是一種新技術(shù)。它依賴的是現(xiàn)有的CSS
/HTML
/Javascript
,而其中最核心的依賴是瀏覽器提供的XMLHttpRequest
對(duì)象,是這個(gè)對(duì)象使得瀏覽器可以發(fā)出HTTP
請(qǐng)求與接收HTTP
響應(yīng)。
所以我用一句話來(lái)總結(jié)兩者的關(guān)系:我們使用XMLHttpRequest
對(duì)象來(lái)發(fā)送一個(gè)Ajax
請(qǐng)求。
XMLHttpRequest的發(fā)展歷程
XMLHttpRequest
一開始只是微軟瀏覽器提供的一個(gè)接口,后來(lái)各大瀏覽器紛紛效仿也提供了這個(gè)接口,再后來(lái)W3C對(duì)它進(jìn)行了標(biāo)準(zhǔn)化,提出了XMLHttpRequest
標(biāo)準(zhǔn)。XMLHttpRequest
標(biāo)準(zhǔn)又分為Level 1
和Level 2
。
XMLHttpRequest Level 1
主要存在以下缺點(diǎn):
-
受同源策略的限制,不能發(fā)送跨域請(qǐng)求;
-
不能發(fā)送二進(jìn)制文件(如圖片、視頻、音頻等),只能發(fā)送純文本數(shù)據(jù);
-
在發(fā)送和獲取數(shù)據(jù)的過(guò)程中,無(wú)法實(shí)時(shí)獲取進(jìn)度信息,只能判斷是否完成;
那么Level 2
對(duì)Level 1
進(jìn)行了改進(jìn),XMLHttpRequest Level 2
中新增了以下功能:
-
可以發(fā)送跨域請(qǐng)求,在服務(wù)端允許的情況下;
-
支持發(fā)送和接收二進(jìn)制數(shù)據(jù);
-
新增formData對(duì)象,支持發(fā)送表單數(shù)據(jù);
-
發(fā)送和獲取數(shù)據(jù)時(shí),可以獲取進(jìn)度信息;
-
可以設(shè)置請(qǐng)求的超時(shí)時(shí)間;
當(dāng)然更詳細(xì)的對(duì)比介紹,可以參考阮老師的這篇文章,文章中對(duì)新增的功能都有具體代碼示例。
XMLHttpRequest兼容性
關(guān)于xhr
的瀏覽器兼容性,大家可以直接查看“Can I use”這個(gè)網(wǎng)站提供的結(jié)果XMLHttpRequest兼容性,下面提供一個(gè)截圖。
從圖中可以看到:
-
IE8/IE9、Opera Mini 完全不支持
xhr
對(duì)象 -
IE10/IE11部分支持,不支持
xhr.responseType
為json
-
部分瀏覽器不支持設(shè)置請(qǐng)求超時(shí),即無(wú)法使用
xhr.timeout
-
部分瀏覽器不支持
xhr.responseType
為blob
細(xì)說(shuō)XMLHttpRequest如何使用
先來(lái)看一段使用XMLHttpRequest
發(fā)送Ajax
請(qǐng)求的簡(jiǎn)單示例代碼。
function sendAjax() { //構(gòu)造表單數(shù)據(jù) var formData = new FormData(); formData.append('username', 'johndoe'); formData.append('id', 123456); //創(chuàng)建xhr對(duì)象 var xhr = new XMLHttpRequest(); //設(shè)置xhr請(qǐng)求的超時(shí)時(shí)間 xhr.timeout = 3000; //設(shè)置響應(yīng)返回的數(shù)據(jù)格式 xhr.responseType = "text"; //創(chuàng)建一個(gè) post 請(qǐng)求,采用異步 xhr.open('POST', '/server', true); //注冊(cè)相關(guān)事件回調(diào)處理函數(shù) xhr.onload = function(e) { if(this.status == 200||this.status == 304){ alert(this.responseText); } }; xhr.ontimeout = function(e) { ... }; xhr.onerror = function(e) { ... }; xhr.upload.onprogress = function(e) { ... }; //發(fā)送數(shù)據(jù) xhr.send(formData); }
上面是一個(gè)使用xhr
發(fā)送表單數(shù)據(jù)的示例,整個(gè)流程可以參考注釋。
接下來(lái)我將站在使用者的角度,以問(wèn)題的形式介紹
xhr
的基本使用。
我對(duì)每一個(gè)問(wèn)題涉及到的知識(shí)點(diǎn)都會(huì)進(jìn)行比較細(xì)致地介紹,有些知識(shí)點(diǎn)可能是你平時(shí)忽略關(guān)注的。
如何設(shè)置request header
在發(fā)送Ajax
請(qǐng)求(實(shí)質(zhì)是一個(gè)HTTP請(qǐng)求)時(shí),我們可能需要設(shè)置一些請(qǐng)求頭部信息,比如content-type
、connection
、cookie
、accept-xxx
等。xhr
提供了setRequestHeader
來(lái)允許我們修改請(qǐng)求 header。
void setRequestHeader(DOMString header, DOMString value);
注意點(diǎn):
-
方法的第一個(gè)參數(shù) header 大小寫不敏感,即可以寫成
content-type
,也可以寫成Content-Type
,甚至寫成content-Type
; -
Content-Type
的默認(rèn)值與具體發(fā)送的數(shù)據(jù)類型有關(guān),請(qǐng)參考本文【可以發(fā)送什么類型的數(shù)據(jù)】一節(jié); -
setRequestHeader
必須在open()
方法之后,send()
方法之前調(diào)用,否則會(huì)拋錯(cuò); -
setRequestHeader
可以調(diào)用多次,最終的值不會(huì)采用覆蓋override
的方式,而是采用追加append
的方式。下面是一個(gè)示例代碼:
var client = new XMLHttpRequest(); client.open('GET', 'demo.cgi'); client.setRequestHeader('X-Test', 'one'); client.setRequestHeader('X-Test', 'two'); // 最終request header中"X-Test"為: one, two client.send();
如何獲取response header
xhr
提供了2個(gè)用來(lái)獲取響應(yīng)頭部的方法:getAllResponseHeaders
和getResponseHeader
。前者是獲取 response 中的所有header 字段,后者只是獲取某個(gè)指定 header 字段的值。另外,getResponseHeader(header)
的header
參數(shù)不區(qū)分大小寫。
DOMString getAllResponseHeaders();
DOMString getResponseHeader(DOMString header);
這2個(gè)方法看起來(lái)簡(jiǎn)單,但卻處處是坑兒。
你是否遇到過(guò)下面的坑兒?——反正我是遇到了。。。
-
使用
getAllResponseHeaders()
看到的所有response header
與實(shí)際在控制臺(tái)Network
中看到的response header
不一樣 -
使用
getResponseHeader()
獲取某個(gè)header
的值時(shí),瀏覽器拋錯(cuò)Refused to get unsafe header "XXX"
經(jīng)過(guò)一番尋找最終在 Stack Overflow找到了答案。
-
原因1:W3C的 xhr 標(biāo)準(zhǔn)中做了限制,規(guī)定客戶端無(wú)法獲取 response 中的
Set-Cookie
、Set-Cookie2
這2個(gè)字段,無(wú)論是同域還是跨域請(qǐng)求; -
原因2:W3C 的 cors 標(biāo)準(zhǔn)對(duì)于跨域請(qǐng)求也做了限制,規(guī)定對(duì)于跨域請(qǐng)求,客戶端允許獲取的response header字段只限于“
simple response header
”和“Access-Control-Expose-Headers
” (兩個(gè)名詞的解釋見下方)。
"
simple response header
"包括的 header 字段有:Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
,Pragma
;
"Access-Control-Expose-Headers
":首先得注意是"Access-Control-Expose-Headers
"進(jìn)行跨域請(qǐng)求時(shí)響應(yīng)頭部中的一個(gè)字段,對(duì)于同域請(qǐng)求,響應(yīng)頭部是沒有這個(gè)字段的。這個(gè)字段中列舉的 header 字段就是服務(wù)器允許暴露給客戶端訪問(wèn)的字段。
所以getAllResponseHeaders()
只能拿到限制以外(即被視為safe
)的header字段,而不是全部字段;而調(diào)用getResponseHeader(header)
方法時(shí),header
參數(shù)必須是限制以外的header字段,否則調(diào)用就會(huì)報(bào)Refused to get unsafe header
的錯(cuò)誤。
如何指定xhr.response的數(shù)據(jù)類型
有些時(shí)候我們希望xhr.response
返回的就是我們想要的數(shù)據(jù)類型。比如:響應(yīng)返回的數(shù)據(jù)是純JSON字符串,但我們期望最終通過(guò)xhr.response
拿到的直接就是一個(gè) js 對(duì)象,我們?cè)撛趺磳?shí)現(xiàn)呢?
有2種方法可以實(shí)現(xiàn),一個(gè)是level 1
就提供的overrideMimeType()
方法,另一個(gè)是level 2
才提供的xhr.responseType
屬性。
xhr.overrideMimeType()
overrideMimeType
是xhr level 1
就有的方法,所以瀏覽器兼容性良好。這個(gè)方法的作用就是用來(lái)重寫response
的content-type
,這樣做有什么意義呢?比如:server 端給客戶端返回了一份document
或者是 xml
文檔,我們希望最終通過(guò)xhr.response
拿到的就是一個(gè)DOM
對(duì)象,那么就可以用xhr.overrideMimeType('text/xml; charset = utf-8')
來(lái)實(shí)現(xiàn)。
再舉一個(gè)使用場(chǎng)景,我們都知道xhr level 1
不支持直接傳輸blob二進(jìn)制數(shù)據(jù),那如果真要傳輸 blob 該怎么辦呢?當(dāng)時(shí)就是利用overrideMimeType
方法來(lái)解決這個(gè)問(wèn)題的。
下面是一個(gè)獲取圖片文件的代碼示例:
var xhr = new XMLHttpRequest(); //向 server 端獲取一張圖片 xhr.open('GET', '/path/to/image.png', true); // 這行是關(guān)鍵! //將響應(yīng)數(shù)據(jù)按照純文本格式來(lái)解析,字符集替換為用戶自己定義的字符集 xhr.overrideMimeType('text/plain; charset=x-user-defined'); xhr.onreadystatechange = function(e) { if (this.readyState == 4 && this.status == 200) { //通過(guò) responseText 來(lái)獲取圖片文件對(duì)應(yīng)的二進(jìn)制字符串 var binStr = this.responseText; //然后自己再想方法將逐個(gè)字節(jié)還原為二進(jìn)制數(shù)據(jù) for (var i = 0, len = binStr.length; i < len; ++i) { var c = binStr.charCodeAt(i); //String.fromCharCode(c & 0xff); var byte = c & 0xff; } } }; xhr.send();
代碼示例中xhr
請(qǐng)求的是一張圖片,通過(guò)將 response
的 content-type
改為'text/plain; charset=x-user-defined',使得 xhr
以純文本格式來(lái)解析接收到的blob 數(shù)據(jù),最終用戶通過(guò)this.responseText
拿到的就是圖片文件對(duì)應(yīng)的二進(jìn)制字符串,最后再將其轉(zhuǎn)換為 blob 數(shù)據(jù)。
xhr.responseType
responseType
是xhr level 2
新增的屬性,用來(lái)指定xhr.response
的數(shù)據(jù)類型,目前還存在些兼容性問(wèn)題,可以參考本文的【XMLHttpRequest
的兼容性】這一小節(jié)。那么responseType
可以設(shè)置為哪些格式呢,我簡(jiǎn)單做了一個(gè)表,如下:
值 | xhr.response 數(shù)據(jù)類型 |
說(shuō)明 |
---|---|---|
"" |
String 字符串 |
默認(rèn)值(在不設(shè)置responseType 時(shí)) |
"text" |
String 字符串 |
|
"document" |
Document 對(duì)象 |
希望返回 XML 格式數(shù)據(jù)時(shí)使用 |
"json" |
javascript 對(duì)象 |
存在兼容性問(wèn)題,IE10/IE11不支持 |
"blob" |
Blob 對(duì)象 |
|
"arrayBuffer" |
ArrayBuffer 對(duì)象 |
下面是同樣是獲取一張圖片的代碼示例,相比xhr.overrideMimeType
,用xhr.response
來(lái)實(shí)現(xiàn)簡(jiǎn)單得多。
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); //可以將`xhr.responseType`設(shè)置為`"blob"`也可以設(shè)置為`" arrayBuffer"` //xhr.responseType = 'arrayBuffer'; xhr.responseType = 'blob'; xhr.onload = function(e) { if (this.status == 200) { var blob = this.response; ... } }; xhr.send();
小結(jié)
雖然在xhr level 2
中,2者是共同存在的。但其實(shí)不難發(fā)現(xiàn),xhr.responseType
就是用來(lái)取代xhr.overrideMimeType()
的,xhr.responseType
功能強(qiáng)大的多,xhr.overrideMimeType()
能做到的xhr.responseType
都能做到。所以我們現(xiàn)在完全可以摒棄使用xhr.overrideMimeType()
了。
如何獲取response數(shù)據(jù)
xhr
提供了3個(gè)屬性來(lái)獲取請(qǐng)求返回的數(shù)據(jù),分別是:xhr.response
、xhr.responseText
、xhr.responseXML
-
xhr.response
-
默認(rèn)值:空字符串
""
-
當(dāng)請(qǐng)求完成時(shí),此屬性才有正確的值
-
請(qǐng)求未完成時(shí),此屬性的值可能是
""
或者null
,具體與xhr.responseType
有關(guān):當(dāng)responseType
為""
或"text"
時(shí),值為""
;responseType
為其他值時(shí),值為null
-
-
xhr.responseText
-
默認(rèn)值為空字符串
""
-
只有當(dāng)
responseType
為"text"
、""
時(shí),xhr
對(duì)象上才有此屬性,此時(shí)才能調(diào)用xhr.responseText
,否則拋錯(cuò) -
只有當(dāng)請(qǐng)求成功時(shí),才能拿到正確值。以下2種情況下值都為空字符串
""
:請(qǐng)求未完成、請(qǐng)求失敗
-
-
xhr.responseXML
-
默認(rèn)值為
null
-
只有當(dāng)
responseType
為"text"
、""
、"document"
時(shí),xhr
對(duì)象上才有此屬性,此時(shí)才能調(diào)用xhr.responseXML
,否則拋錯(cuò) -
只有當(dāng)請(qǐng)求成功且返回?cái)?shù)據(jù)被正確解析時(shí),才能拿到正確值。以下3種情況下值都為
null
:請(qǐng)求未完成、請(qǐng)求失敗、請(qǐng)求成功但返回?cái)?shù)據(jù)無(wú)法被正確解析時(shí)
-
如何追蹤ajax請(qǐng)求的當(dāng)前狀態(tài)
在發(fā)一個(gè)ajax
請(qǐng)求后,如果想追蹤請(qǐng)求當(dāng)前處于哪種狀態(tài),該怎么做呢?
用xhr.readyState
這個(gè)屬性即可追蹤到。這個(gè)屬性是只讀屬性,總共有5種可能值,分別對(duì)應(yīng)xhr
不同的不同階段。每次xhr.readyState
的值發(fā)生變化時(shí),都會(huì)觸發(fā)xhr.onreadystatechange
事件,我們可以在這個(gè)事件中進(jìn)行相關(guān)狀態(tài)判斷。
xhr.onreadystatechange = function () { switch(xhr.readyState){ case 1://OPENED //do something break; case 2://HEADERS_RECEIVED //do something break; case 3://LOADING //do something break; case 4://DONE //do something break; }
值 | 狀態(tài) | 描述 |
---|---|---|
0 |
UNSENT (初始狀態(tài),未打開) |
此時(shí)xhr 對(duì)象被成功構(gòu)造,open() 方法還未被調(diào)用 |
1 |
OPENED (已打開,未發(fā)送) |
open() 方法已被成功調(diào)用,send() 方法還未被調(diào)用。注意:只有xhr 處于OPENED 狀態(tài),才能調(diào)用xhr.setRequestHeader() 和xhr.send() ,否則會(huì)報(bào)錯(cuò) |
2 |
HEADERS_RECEIVED (已獲取響應(yīng)頭) |
send() 方法已經(jīng)被調(diào)用, 響應(yīng)頭和響應(yīng)狀態(tài)已經(jīng)返回 |
3 |
LOADING (正在下載響應(yīng)體) |
響應(yīng)體(response entity body )正在下載中,此狀態(tài)下通過(guò)xhr.response 可能已經(jīng)有了響應(yīng)數(shù)據(jù) |
4 |
DONE (整個(gè)數(shù)據(jù)傳輸過(guò)程結(jié)束) |
整個(gè)數(shù)據(jù)傳輸過(guò)程結(jié)束,不管本次請(qǐng)求是成功還是失敗 |
如何設(shè)置請(qǐng)求的超時(shí)時(shí)間
如果請(qǐng)求過(guò)了很久還沒有成功,為了不會(huì)白白占用的網(wǎng)絡(luò)資源,我們一般會(huì)主動(dòng)終止請(qǐng)求。XMLHttpRequest
提供了timeout
屬性來(lái)允許設(shè)置請(qǐng)求的超時(shí)時(shí)間。
xhr.timeout
單位:milliseconds 毫秒
默認(rèn)值:0
,即不設(shè)置超時(shí)
很多同學(xué)都知道:從請(qǐng)求開始 算起,若超過(guò) timeout
時(shí)間請(qǐng)求還沒有結(jié)束(包括成功/失敗),則會(huì)觸發(fā)ontimeout事件,主動(dòng)結(jié)束該請(qǐng)求。
【那么到底什么時(shí)候才算是請(qǐng)求開始 ?】
——xhr.onloadstart
事件觸發(fā)的時(shí)候,也就是你調(diào)用xhr.send()
方法的時(shí)候。
因?yàn)?code>xhr.open()只是創(chuàng)建了一個(gè)連接,但并沒有真正開始數(shù)據(jù)的傳輸,而xhr.send()
才是真正開始了數(shù)據(jù)的傳輸過(guò)程。只有調(diào)用了xhr.send()
,才會(huì)觸發(fā)xhr.onloadstart
。
【那么什么時(shí)候才算是請(qǐng)求結(jié)束 ?】
—— xhr.loadend
事件觸發(fā)的時(shí)候。
另外,還有2個(gè)需要注意的坑兒:
-
可以在
send()
之后再設(shè)置此xhr.timeout
,但計(jì)時(shí)起始點(diǎn)仍為調(diào)用xhr.send()
方法的時(shí)刻。 -
當(dāng)
xhr
為一個(gè)sync
同步請(qǐng)求時(shí),xhr.timeout
必須置為0
,否則會(huì)拋錯(cuò)。原因可以參考本文的【如何發(fā)一個(gè)同步請(qǐng)求】一節(jié)。
如何發(fā)一個(gè)同步請(qǐng)求
xhr
默認(rèn)發(fā)的是異步請(qǐng)求,但也支持發(fā)同步請(qǐng)求(當(dāng)然實(shí)際開發(fā)中應(yīng)該盡量避免使用)。到底是異步還是同步請(qǐng)求,由xhr.open()
傳入的async
參數(shù)決定。
open(method, url [, async = true [, username = null [, password = null]]])
-
method
: 請(qǐng)求的方式,如GET/POST/HEADER
等,這個(gè)參數(shù)不區(qū)分大小寫 -
url
: 請(qǐng)求的地址,可以是相對(duì)地址如example.php
,這個(gè)相對(duì)是相對(duì)于當(dāng)前網(wǎng)頁(yè)的url
路徑;也可以是絕對(duì)地址如http://www.example.com/example.php
-
async
: 默認(rèn)值為true
,即為異步請(qǐng)求,若async=false
,則為同步請(qǐng)求
在我認(rèn)真研讀W3C 的 xhr 標(biāo)準(zhǔn)前,我總以為同步請(qǐng)求和異步請(qǐng)求只是阻塞和非阻塞的區(qū)別,其他什么事件觸發(fā)、參數(shù)設(shè)置應(yīng)該是一樣的,事實(shí)證明我錯(cuò)了。
W3C 的 xhr標(biāo)準(zhǔn)中關(guān)于open()
方法有這樣一段說(shuō)明:
Throws an "InvalidAccessError" exception if async is false, the JavaScript global environment is a document environment, and either the timeout attribute is not zero, the withCredentials attribute is true, or the responseType attribute is not the empty string.
從上面一段說(shuō)明可以知道,當(dāng)xhr
為同步請(qǐng)求時(shí),有如下限制:
-
xhr.timeout
必須為0
-
xhr.withCredentials
必須為false
-
xhr.responseType
必須為""
(注意置為"text"
也不允許)
若上面任何一個(gè)限制不滿足,都會(huì)拋錯(cuò),而對(duì)于異步請(qǐng)求,則沒有這些參數(shù)設(shè)置上的限制。
之前說(shuō)過(guò)頁(yè)面中應(yīng)該盡量避免使用sync
同步請(qǐng)求,為什么呢?
因?yàn)槲覀儫o(wú)法設(shè)置請(qǐng)求超時(shí)時(shí)間(xhr.timeout
為0
,即不限時(shí))。在不限制超時(shí)的情況下,有可能同步請(qǐng)求一直處于pending
狀態(tài),服務(wù)端遲遲不返回響應(yīng),這樣整個(gè)頁(yè)面就會(huì)一直阻塞,無(wú)法響應(yīng)用戶的其他交互。
另外,標(biāo)準(zhǔn)中并沒有提及同步請(qǐng)求時(shí)事件觸發(fā)的限制,但實(shí)際開發(fā)中我確實(shí)遇到過(guò)部分應(yīng)該觸發(fā)的事件并沒有觸發(fā)的現(xiàn)象。如在 chrome中,當(dāng)xhr
為同步請(qǐng)求時(shí),在xhr.readyState
由2
變成3
時(shí),并不會(huì)觸發(fā) onreadystatechange
事件,xhr.upload.onprogress
和 xhr.onprogress
事件也不會(huì)觸發(fā)。
如何獲取上傳、下載的進(jìn)度
在上傳或者下載比較大的文件時(shí),實(shí)時(shí)顯示當(dāng)前的上傳、下載進(jìn)度是很普遍的產(chǎn)品需求。
我們可以通過(guò)onprogress
事件來(lái)實(shí)時(shí)顯示進(jìn)度,默認(rèn)情況下這個(gè)事件每50ms觸發(fā)一次。需要注意的是,上傳過(guò)程和下載過(guò)程觸發(fā)的是不同對(duì)象的onprogress
事件:
-
上傳觸發(fā)的是
xhr.upload
對(duì)象的onprogress
事件 -
下載觸發(fā)的是
xhr
對(duì)象的onprogress
事件
xhr.onprogress = updateProgress; xhr.upload.onprogress = updateProgress; function updateProgress(event) { if (event.lengthComputable) { var completedPercent = event.loaded / event.total; } }
可以發(fā)送什么類型的數(shù)據(jù)
void send(data);
xhr.send(data)
的參數(shù)data可以是以下幾種類型:
-
ArrayBuffer
-
Blob
-
Document
-
DOMString
-
FormData
-
null
如果是 GET/HEAD請(qǐng)求,send()
方法一般不傳參或傳 null
。不過(guò)即使你真?zhèn)魅肓藚?shù),參數(shù)也最終被忽略,xhr.send(data)
中的data會(huì)被置為 null
.
xhr.send(data)
中data參數(shù)的數(shù)據(jù)類型會(huì)影響請(qǐng)求頭部content-type
的默認(rèn)值:
-
如果
data
是Document
類型,同時(shí)也是HTML Document
類型,則content-type
默認(rèn)值為text/html;charset=UTF-8
;否則為application/xml;charset=UTF-8
; -
如果
data
是DOMString
類型,content-type
默認(rèn)值為text/plain;charset=UTF-8
; -
如果
data
是FormData
類型,content-type
默認(rèn)值為multipart/form-data; boundary=[xxx]
-
如果
data
是其他類型,則不會(huì)設(shè)置content-type
的默認(rèn)值
當(dāng)然這些只是content-type
的默認(rèn)值,但如果用xhr.setRequestHeader()
手動(dòng)設(shè)置了中content-type
的值,以上默認(rèn)值就會(huì)被覆蓋。
另外需要注意的是,若在斷網(wǎng)狀態(tài)下調(diào)用xhr.send(data)
方法,則會(huì)拋錯(cuò):Uncaught NetworkError: Failed to execute 'send' on 'XMLHttpRequest'
。一旦程序拋出錯(cuò)誤,如果不 catch 就無(wú)法繼續(xù)執(zhí)行后面的代碼,所以調(diào)用 xhr.send(data)
方法時(shí),應(yīng)該用 try-catch
捕捉錯(cuò)誤。
try{ xhr.send(data) }catch(e) { //doSomething... };
xhr.withCredentials與 CORS 什么關(guān)系
我們都知道,在發(fā)同域請(qǐng)求時(shí),瀏覽器會(huì)將
cookie
自動(dòng)加在request header
中。但大家是否遇到過(guò)這樣的場(chǎng)景:在發(fā)送跨域請(qǐng)求時(shí),cookie
并沒有自動(dòng)加在request header
中。
造成這個(gè)問(wèn)題的原因是:在CORS
標(biāo)準(zhǔn)中做了規(guī)定,默認(rèn)情況下,瀏覽器在發(fā)送跨域請(qǐng)求時(shí),不能發(fā)送任何認(rèn)證信息(credentials
)如"cookies
"和"HTTP authentication schemes
"。除非xhr.withCredentials
為true
(xhr
對(duì)象有一個(gè)屬性叫withCredentials
,默認(rèn)值為false
)。
所以根本原因是cookies
也是一種認(rèn)證信息,在跨域請(qǐng)求中,client
端必須手動(dòng)設(shè)置xhr.withCredentials=true
,且server
端也必須允許request
能攜帶認(rèn)證信息(即response header
中包含Access-Control-Allow-Credentials:true
),這樣瀏覽器才會(huì)自動(dòng)將cookie
加在request header
中。
另外,要特別注意一點(diǎn),一旦跨域request
能夠攜帶認(rèn)證信息,server
端一定不能將Access-Control-Allow-Origin
設(shè)置為*
,而必須設(shè)置為請(qǐng)求頁(yè)面的域名。
xhr相關(guān)事件
事件分類
xhr
相關(guān)事件有很多,有時(shí)記起來(lái)還挺容易混亂。但當(dāng)我了解了具體代碼實(shí)現(xiàn)后,就容易理清楚了。下面是XMLHttpRequest
的部分實(shí)現(xiàn)代碼:
interface XMLHttpRequestEventTarget : EventTarget { // event handlers attribute EventHandler onloadstart; attribute EventHandler onprogress; attribute EventHandler onabort; attribute EventHandler onerror; attribute EventHandler onload; attribute EventHandler ontimeout; attribute EventHandler onloadend; }; interface XMLHttpRequestUpload : XMLHttpRequestEventTarget { }; interface XMLHttpRequest : XMLHttpRequestEventTarget { // event handler attribute EventHandler onreadystatechange; readonly attribute XMLHttpRequestUpload upload; };
從代碼中我們可以看出:
-
XMLHttpRequestEventTarget
接口定義了7個(gè)事件:-
onloadstart
-
onprogress
-
onabort
-
ontimeout
-
onerror
-
onload
-
onloadend
-
-
每一個(gè)
XMLHttpRequest
里面都有一個(gè)upload
屬性,而upload
是一個(gè)XMLHttpRequestUpload
對(duì)象 -
XMLHttpRequest
和XMLHttpRequestUpload
都繼承了同一個(gè)XMLHttpRequestEventTarget
接口,所以xhr
和xhr.upload
都有第一條列舉的7個(gè)事件 -
onreadystatechange
是XMLHttpRequest
獨(dú)有的事件
所以這么一看就很清晰了:
xhr
一共有8個(gè)相關(guān)事件:7個(gè)XMLHttpRequestEventTarget
事件+1個(gè)獨(dú)有的onreadystatechange
事件;而xhr.upload
只有7個(gè)XMLHttpRequestEventTarget
事件。
事件觸發(fā)條件
下面是我自己整理的一張xhr
相關(guān)事件觸發(fā)條件表,其中最需要注意的是 onerror
事件的觸發(fā)條件。
事件 | 觸發(fā)條件 |
---|---|
onreadystatechange |
每當(dāng)xhr.readyState 改變時(shí)觸發(fā);但xhr.readyState 由非0 值變?yōu)?code>0時(shí)不觸發(fā)。 |
onloadstart |
調(diào)用xhr.send() 方法后立即觸發(fā),若xhr.send() 未被調(diào)用則不會(huì)觸發(fā)此事件。 |
onprogress |
xhr.upload.onprogress 在上傳階段(即xhr.send() 之后,xhr.readystate=2 之前)觸發(fā),每50ms觸發(fā)一次;xhr.onprogress 在下載階段(即xhr.readystate=3 時(shí))觸發(fā),每50ms觸發(fā)一次。 |
onload |
當(dāng)請(qǐng)求成功完成時(shí)觸發(fā),此時(shí)xhr.readystate=4 |
onloadend |
當(dāng)請(qǐng)求結(jié)束(包括請(qǐng)求成功和請(qǐng)求失?。r(shí)觸發(fā) |
onabort |
當(dāng)調(diào)用xhr.abort() 后觸發(fā) |
ontimeout |
xhr.timeout 不等于0,由請(qǐng)求開始即onloadstart 開始算起,當(dāng)?shù)竭_(dá)xhr.timeout 所設(shè)置時(shí)間請(qǐng)求還未結(jié)束即onloadend ,則觸發(fā)此事件。 |
onerror |
在請(qǐng)求過(guò)程中,若發(fā)生Network error 則會(huì)觸發(fā)此事件(若發(fā)生Network error 時(shí),上傳還沒有結(jié)束,則會(huì)先觸發(fā)xhr.upload.onerror ,再觸發(fā)xhr.onerror ;若發(fā)生Network error 時(shí),上傳已經(jīng)結(jié)束,則只會(huì)觸發(fā)xhr.onerror )。注意,只有發(fā)生了網(wǎng)絡(luò)層級(jí)別的異常才會(huì)觸發(fā)此事件,對(duì)于應(yīng)用層級(jí)別的異常,如響應(yīng)返回的xhr.statusCode 是4xx 時(shí),并不屬于Network error ,所以不會(huì)觸發(fā)onerror 事件,而是會(huì)觸發(fā)onload 事件。 |
事件觸發(fā)順序
當(dāng)請(qǐng)求一切正常時(shí),相關(guān)的事件觸發(fā)順序如下:
-
觸發(fā)
xhr.onreadystatechange
(之后每次readyState
變化時(shí),都會(huì)觸發(fā)一次) -
觸發(fā)
xhr.onloadstart
//上傳階段開始: -
觸發(fā)
xhr.upload.onloadstart
-
觸發(fā)
xhr.upload.onprogress
-
觸發(fā)
xhr.upload.onload
-
觸發(fā)
xhr.upload.onloadend
//上傳結(jié)束,下載階段開始: -
觸發(fā)
xhr.onprogress
-
觸發(fā)
xhr.onload
-
觸發(fā)
xhr.onloadend
發(fā)生abort/timeout/error異常的處理
在請(qǐng)求的過(guò)程中,有可能發(fā)生 abort
/timeout
/error
這3種異常。那么一旦發(fā)生這些異常,xhr
后續(xù)會(huì)進(jìn)行哪些處理呢?后續(xù)處理如下:
-
一旦發(fā)生
abort
或timeout
或error
異常,先立即中止當(dāng)前請(qǐng)求 -
將
readystate
置為4
,并觸發(fā)xhr.onreadystatechange
事件 -
如果上傳階段還沒有結(jié)束,則依次觸發(fā)以下事件:
-
xhr.upload.onprogress
-
xhr.upload.[onabort或ontimeout或onerror]
-
xhr.upload.onloadend
-
-
觸發(fā)
xhr.onprogress
事件 -
觸發(fā)
xhr.[onabort或ontimeout或onerror]
事件 -
觸發(fā)
xhr.onloadend
事件
在哪個(gè)xhr事件中注冊(cè)成功回調(diào)?
從上面介紹的事件中,可以知道若xhr
請(qǐng)求成功,就會(huì)觸發(fā)xhr.onreadystatechange
和xhr.onload
兩個(gè)事件。 那么我們到底要將成功回調(diào)注冊(cè)在哪個(gè)事件中呢?我傾向于 xhr.onload
事件,因?yàn)?code>xhr.onreadystatechange是每次xhr.readyState
變化時(shí)都會(huì)觸發(fā),而不是xhr.readyState=4
時(shí)才觸發(fā)。
xhr.onload = function () { //如果請(qǐng)求成功 if(xhr.status == 200){ //do successCallback } }
上面的示例代碼是很常見的寫法:先判斷http
狀態(tài)碼是否是200
,如果是,則認(rèn)為請(qǐng)求是成功的,接著執(zhí)行成功回調(diào)。這樣的判斷是有坑兒的,比如當(dāng)返回的http
狀態(tài)碼不是200
,而是201
時(shí),請(qǐng)求雖然也是成功的,但并沒有執(zhí)行成功回調(diào)邏輯。所以更靠譜的判斷方法應(yīng)該是:當(dāng)http
狀態(tài)碼為2xx
或304
時(shí)才認(rèn)為成功。
xhr.onload = function () { //如果請(qǐng)求成功 if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ //do successCallback } }
結(jié)語(yǔ)
終于寫完了......
看完那一篇長(zhǎng)長(zhǎng)的W3C的xhr 標(biāo)準(zhǔn),我眼睛都花了......
希望這篇總結(jié)能幫助剛開始接觸XMLHttpRequest
的你。
最后給點(diǎn)擴(kuò)展學(xué)習(xí)資料,如果你:
-
想真正搞懂
XMLHttpRequest
,最靠譜的方法還是看 W3C的xhr 標(biāo)準(zhǔn); -
想結(jié)合代碼學(xué)習(xí)如何用
XMLHttpRequest
發(fā)各種類型的數(shù)據(jù),可以參考html5rocks上的這篇文章 -
想粗略的了解
XMLHttpRequest
的基本使用,可以參考MDN的XMLHttpRequest介紹; -
想了解
XMLHttpRequest
的發(fā)展歷程,可以參考阮老師的文章; -
想了解
Ajax
的基本介紹,可以參考AJAX Tutorial; -
想了解跨域請(qǐng)求,則可以參考W3C的 cors 標(biāo)準(zhǔn);
-
想了解
http
協(xié)議,則可以參考HTTP Tutorial;
更多關(guān)于ajax相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《jquery中Ajax用法總結(jié)》、《JavaScript中ajax操作技巧總結(jié)》、《PHP+ajax技巧與應(yīng)用小結(jié)》及《asp.net ajax技巧總結(jié)專題》
希望本文所述對(duì)大家ajax程序設(shè)計(jì)有所幫助。
- 深入講解xhr(XMLHttpRequest)/jsonp請(qǐng)求之a(chǎn)bort
- javascript創(chuàng)建createXmlHttpRequest對(duì)象示例代碼
- js判斷IE6/IE7/FF的代碼[XMLHttpRequest]
- javascript一個(gè)無(wú)懈可擊的實(shí)例化XMLHttpRequest的方法
- javascript XMLHttpRequest對(duì)象全面剖析
- Javascript+XMLHttpRequest+asp.net無(wú)刷新讀取數(shù)據(jù)庫(kù)數(shù)據(jù)
- JSP XMLHttpRequest動(dòng)態(tài)無(wú)刷新及其中文亂碼處理
- JS中的XMLHttpRequest?對(duì)象示例詳解
相關(guān)文章
JavaScript實(shí)現(xiàn)復(fù)選框全選或全取消操作
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)復(fù)選框全選或全取消操作,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09利用JS動(dòng)態(tài)生成隔行換色HTML表格的兩種方法
這篇文章主要介紹了利用JS動(dòng)態(tài)生成隔行換色HTML表格的兩種方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10JS監(jiān)聽組合按鍵思路及實(shí)現(xiàn)過(guò)程
這篇文章主要介紹了JS監(jiān)聽組合按鍵思路及實(shí)現(xiàn)過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Uniapp中嵌入H5并在H5中跳轉(zhuǎn)到APP的指定頁(yè)面方法詳解
Uniapp是一款基于Vue.js框架的跨平臺(tái)開發(fā)工具,支持在一套代碼中開發(fā)出運(yùn)行于各大平臺(tái)的應(yīng)用程序,這篇文章主要給大家介紹了關(guān)于Uniapp中嵌入H5并在H5中跳轉(zhuǎn)到APP的指定頁(yè)面的相關(guān)資料,需要的朋友可以參考下2023-09-09純js代碼制作的網(wǎng)頁(yè)時(shí)鐘特效【附實(shí)例】
下面小編就為大家?guī)?lái)一篇純js代碼制作的網(wǎng)頁(yè)時(shí)鐘特效【附實(shí)例】。小編覺得聽錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-03-03基于javascript實(shí)現(xiàn)日歷功能原理及代碼實(shí)例
這篇文章主要介紹了基于javascript實(shí)現(xiàn)日歷效果原理及代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05關(guān)于js對(duì)textarea換行符的處理方法淺析
這篇文章主要給大家介紹了關(guān)于js對(duì)textarea換行符的處理方法的相關(guān)資料,文中通過(guò)示例代碼介紹地方非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08