? 概述
XMLHttpRequest
對(duì)象的作用是與服務(wù)器交互叹谁,在不刷新頁(yè)面的情況下請(qǐng)求特定 URL,獲取數(shù)據(jù)煌张。其實(shí)說(shuō)白了,就是用異步的方式退客,向服務(wù)端發(fā)起請(qǐng)求骏融,獲取或提交數(shù)據(jù),如果非要用通俗又粗俗的話說(shuō)萌狂,它就是用來(lái)發(fā)ajax
請(qǐng)求的档玻。
其實(shí)
XMLHttpRequest
對(duì)象也能發(fā)送同步請(qǐng)求,只要把open()
方法的第三個(gè)參數(shù)設(shè)置成false
茫藏,就行误趴。
只是由于同步會(huì)對(duì)用戶體驗(yàn)產(chǎn)生負(fù)面影響,瀏覽器都已經(jīng)把在主線程上的同步請(qǐng)求棄用了务傲。所以凉当,只要記住XMLHttpRequest
對(duì)象只能發(fā)送異步請(qǐng)求就對(duì)了。
XMLHttpRequest
的通信流程是由客戶端發(fā)起的售葡。
如果需要由服務(wù)端發(fā)起向客戶端的推送看杭,可以用server sent events
(這里是大神的教程)。
如果需要全雙工的通信天通,WebSocket
(這里還是大神的教程)可能是更好的選擇泊窘。
?? 運(yùn)轉(zhuǎn)流程
首先,先創(chuàng)建一個(gè)XMLHttpRequest
實(shí)例:
const XHR = new XMLHttpRequest();
然后像寒,調(diào)用open()
方法:
const XHR = new XMLHttpRequest();
XHR.open('GET', '/apis');
這行代碼會(huì)啟動(dòng)一個(gè)針對(duì)/apis
地址(也可以是絕對(duì)路徑)的GET
請(qǐng)求烘豹,要說(shuō)明的是:調(diào)用open()
方法并不會(huì)真正發(fā)送請(qǐng)求,而只是啟動(dòng)一個(gè)請(qǐng)求以備發(fā)送诺祸。
要發(fā)送真正的請(qǐng)求携悯,必須像下面這樣調(diào)用send()
方法:
const XHR = new XMLHttpRequest();
XHR.open('GET', '/apis');
XHR.send(null);
這里的send()
方法接收一個(gè)參數(shù),既要作為請(qǐng)求主體發(fā)送的數(shù)據(jù)筷笨。如果不需要通過(guò)請(qǐng)求主體發(fā)送數(shù)據(jù)憔鬼,則必須傳入null
龟劲,因?yàn)檫@個(gè)參數(shù)對(duì)有些瀏覽器來(lái)說(shuō)是必需的。
因?yàn)槲覀儼l(fā)送的是異步請(qǐng)求轴或,所以要依據(jù)readyState
屬性的變化昌跌,來(lái)了解異步的過(guò)程。該屬性表示請(qǐng)求/響應(yīng)過(guò)程的當(dāng)前活動(dòng)階段照雁,如圖所示:
只要readyState
屬性的值由一個(gè)值變成另一個(gè)值蚕愤,都會(huì)觸發(fā)一次readystatechange
事件。通常饺蚊,我們只對(duì)readyState
值為4的階段感興趣萍诱,因?yàn)檫@時(shí)所有數(shù)據(jù)都已經(jīng)就緒。不過(guò)污呼,必須在調(diào)用open()
之前指定onreadystatechange
事件處理程序才能確痹7唬跨瀏覽器兼容性。
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = () => {
if (XHR.readyState === 4) {
// blah blah blah...
} else { }
}
XHR.open('GET', '/apis');
XHR.send(null);
服務(wù)端接收到請(qǐng)求信息燕酷,并做出響應(yīng)(如果通信正常)籍凝,客戶端在收到響應(yīng)后,響應(yīng)的數(shù)據(jù)會(huì)自動(dòng)填充XHR
對(duì)象的屬性悟狱,比如:status
(響應(yīng)的HTTP狀態(tài))静浴、responseText
(作為響應(yīng)主體被返回的文本)......。
一般來(lái)說(shuō)挤渐,可以將HTTP狀態(tài)碼為200作為成功的標(biāo)志苹享。此外,狀態(tài)代碼為304表示請(qǐng)求的資源并沒(méi)有被修改浴麻,可以直接使用瀏覽器中緩存的版本得问,當(dāng)然,也意味著響應(yīng)是有效的软免。
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = () => {
if (XHR.readyState === 4) {
if (XHR.status < 300 || XHR.status === 304) {
// blah blah blah...
} else {
alert(`請(qǐng)求失敗了:${XHR.status}`);
}
} else { }
}
XHR.open('GET', '/apis');
XHR.send(null);
此處要說(shuō)明一下:狀態(tài)碼為304的意思是說(shuō)瀏覽器可以用緩存的版本宫纬,但是不代表瀏覽器沒(méi)有向服務(wù)端發(fā)起請(qǐng)求。其實(shí)膏萧,瀏覽器是真真正正地向服務(wù)端發(fā)了請(qǐng)求的漓骚,只是服務(wù)端驗(yàn)證了資源的過(guò)期時(shí)間后,僅僅是告訴瀏覽器:“我就不給你數(shù)據(jù)了榛泛,你用你自己那份資源吧蝌蹂,還在保質(zhì)期呢〔芟牵”
另外孤个,在接收到響應(yīng)之前還可以調(diào)用abort()
方法,來(lái)取消異步請(qǐng)求沛简。在終止請(qǐng)求后齐鲤,由于內(nèi)存原因斥废,還應(yīng)該對(duì)XHR
對(duì)象進(jìn)行解引用操作。
上面给郊,是使用XMLHttpRequest
的最簡(jiǎn)單流程牡肉。
下面,再補(bǔ)充一些細(xì)節(jié)操作:
-
添加/獲取頭部信息
每個(gè)HTTP請(qǐng)求和響應(yīng)都會(huì)帶有相應(yīng)的頭部信息(有的有用丑罪,有的對(duì)開(kāi)發(fā)人員沒(méi)用)荚板,XMLHttpRequest
對(duì)象也提供了操作這兩種頭部(即請(qǐng)求頭部和響應(yīng)頭部)信息的方法凤壁。
XMLHttpRequest
的setRequestHeader()
方法可以設(shè)置自定義的請(qǐng)求頭部信息吩屹,要成功發(fā)送請(qǐng)求頭部信息,此方法必須在調(diào)用open()
方法之后且調(diào)用send()
方法之前調(diào)用拧抖。
調(diào)用XMLHttpRequest
的getResponseHeader()
方法并傳入頭部字段名稱煤搜,可以取得相應(yīng)的響應(yīng)頭部信息。而調(diào)用getAllResponseHeaders()
方法則可以取得一個(gè)包含所用頭部信息的長(zhǎng)字符串唧席。這兩個(gè)方法也要在調(diào)用open()
方法之后且調(diào)用send()
方法之前調(diào)用擦盾。
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = () => {
if (XHR.readyState === 4) {
if (XHR.status < 300 || XHR.status === 304) {
// blah blah blah...
} else {
alert(`請(qǐng)求失敗了:${XHR.status}`);
}
} else { }
}
XHR.open('GET', '/apis');
XHR.setRequestHeader('請(qǐng)求頭的自定義字段名稱', '頭部字段的值');
XHR.getResponseHeader('響應(yīng)頭的字段名稱');
XHR.getAllResponseHeaders();
XHR.send(null);
為什么要添加/獲取頭信息呢?
此之目的僅僅是為了方便信息的傳輸淌哟。服務(wù)器在接收到自定義的頭部信息后迹卢,可以執(zhí)行相應(yīng)的后續(xù)操作;同樣的徒仓,在服務(wù)端腐碱,也可以利用頭部信息向?yàn)g覽器發(fā)送額外的、結(jié)構(gòu)化的數(shù)據(jù)掉弛。而且症见,不是每次請(qǐng)求都必須做這種操作,要看使用場(chǎng)景殃饿,需要用的時(shí)候再用谋作。
-
監(jiān)測(cè)進(jìn)度
XMLHttpRequest
提供了各種在請(qǐng)求被處理期間發(fā)生的事件以供監(jiān)聽(tīng)。這包括定期進(jìn)度通知乎芳、 錯(cuò)誤通知遵蚜,等等。
const XHR = new XMLHttpRequest();
/**
* 監(jiān)聽(tīng)請(qǐng)求過(guò)程中的各種事件
*/
XHR.onprogress = (oEvent) => {
// 服務(wù)端到客戶端的傳輸進(jìn)程(下載)
// 此時(shí)readyState=2
if (oEvent.lengthComputable) {
const percentComplete = oEvent.loaded / oEvent.total * 100;
// blah blah blah...
} else {
// 總大小未知時(shí)不能計(jì)算進(jìn)程信息
}
};
XHR.onload = (oEvent) => {
// 傳輸完成奈惑,所有數(shù)據(jù)保存在response中吭净。
// 此時(shí)readyState=4
// blah blah blah...
};
XHR.onerror = (oEvent) => {
// 當(dāng)request遭遇錯(cuò)誤時(shí)觸發(fā)。
// blah blah blah...
};
XHR.onabort= (oEvent) => {
// 當(dāng)request被停止時(shí)觸發(fā)携取。
// blah blah blah...
};
XHR.onreadystatechange = () => {
if (XHR.readyState === 4) {
if (XHR.status < 300 || XHR.status === 304) {
// blah blah blah...
} else {
alert(`請(qǐng)求失敗了:${XHR.status}`);
}
} else { }
}
XHR.open('GET', '/a.txt');
XHR.send(null);
你需要在請(qǐng)求調(diào)用
open()
之前添加事件監(jiān)聽(tīng)攒钳。否則progress
事件將不會(huì)被觸發(fā)。
progress
事件同時(shí)存在于下載和上傳的傳輸雷滋。上面的例子是針對(duì)下載的相關(guān)事件不撑。上傳相關(guān)事件在 XMLHttpRequest.upload
對(duì)象上被觸發(fā)文兢,像下面這樣:
var oReq = new XMLHttpRequest();
oReq.upload.addEventListener("progress", updateProgress);
oReq.upload.addEventListener("load" , transferComplete);
oReq.upload.addEventListener("error", transferFailed );
oReq.upload.addEventListener("abort", transferCanceled);
oReq.open();
?? 示例
- ?? 發(fā)送一個(gè)
GET
請(qǐng)求
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = () => {
if (XHR.readyState === 4) {
if (XHR.status < 300 || XHR.status === 304) {
console.dir(XHR.response);
} else { }
} else { }
}
XHR.open('GET', '/apis');
XHR.send(null);
結(jié)果如圖所示:
給請(qǐng)求加點(diǎn)參數(shù):
XHR.open("GET", "/apis?ab=13&page=1");
XHR.setRequestHeader('CC', 989);
XHR.send(null);
結(jié)果如圖所示:
- ?? 發(fā)送一個(gè)
POST
請(qǐng)求
const XHR = new XMLHttpRequest();
XHR.onreadystatechange = () => {
if (XHR.readyState === 4) {
if (XHR.status < 300 || XHR.status === 304) {
console.dir(XHR.response);
} else { }
} else { }
}
XHR.open('POST', '/apis');
XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
XHR.send('name=張三豐&職務(wù)=掌門');
結(jié)果如圖所示:
為什么要添加
Content-Type
請(qǐng)求頭信息呢?需要設(shè)置
Content-Type
頭信息焕檬,完全是由于服務(wù)端的原因姆坚。因?yàn)闊o(wú)論是表單、Ajax实愚、jsonp兼呵,到了服務(wù)端那兒,它只認(rèn)一種請(qǐng)求腊敲,就是form表單請(qǐng)求击喂,所以Ajax發(fā)送的POST請(qǐng)求,服務(wù)端會(huì)認(rèn)成是form表單的POST請(qǐng)求碰辅,而form表單發(fā)送POST請(qǐng)求時(shí)懂昂,如果不設(shè)置enctype
屬性,就會(huì)以默認(rèn)的application/x-www-form-urlencoded
方式提交表單没宾,因此不加不行啊凌彬。如果要上傳文件,需要將
Content-Type
頭的值設(shè)置為multipart/form-data
循衰,總之铲敛,跟form表單的提交方式保持一致就對(duì)了。
這篇《Express.js 解析 Post 數(shù)據(jù)類型的正確姿勢(shì)》介紹了POST請(qǐng)求的四種方式会钝,值得一看??伐蒋。
好了,先寫(xiě)到這兒吧顽素。
--(完)--