摘要:默認參數(shù)為空字符串密碼,可選參數(shù),用于授權(quán)。默認參數(shù)為空字符串備注如果不是有效的方法或地址不能被成功解析,將會拋出異常如果請求方法不區(qū)分大小寫為或?qū)伋霎惓V貙懹煞?wù)器返回的類型。
本文詳細介紹了 XMLHttpRequest 相關(guān)知識,涉及內(nèi)容:
AJAX、XMLHTTP、XMLHttpRequest詳解、XMLHttpRequest Level 1、Level 2 詳解
XHR 上傳、下載數(shù)據(jù)、XHR 流式傳輸、XHR 定時輪詢和長輪詢區(qū)別與優(yōu)缺點、XMLHttpRequest 庫 (Mock.js、Zone.js、Oboe.js、fetch.js)
XMLHttpRequest 常用代碼片段:
ArrayBuffer 對象轉(zhuǎn)字符串
字符串轉(zhuǎn) ArrayBuffer 對象
創(chuàng)建 XHR 對象
sendAsBinary() polyfill
獲取 XMLHttpRequest 響應(yīng)體
獲取 responseURL
驗證請求是否成功
解析查詢參數(shù)為Map對象
XHR 下載圖片
XHR 上傳圖片
XHR 上傳進度條
分析 AJAX 請求狀態(tài)為 0、GET請求方式為什么不能通過send() 方法發(fā)送請求體、簡單請求和預(yù)請求、XMLHttpRequest對象垃圾回收機制、Get與Post請求區(qū)別、如何避免重復(fù)發(fā)送請求、AJAX 站點 SEO 優(yōu)化等問題。
AJAX AJAX 定義AJAX 應(yīng)用AJAX即“Asynchronous JavaScript and XML”(異步的JavaScript與XML技術(shù)),指的是一套綜合了多項技術(shù)的瀏覽器端網(wǎng)頁開發(fā)技術(shù)。Ajax的概念由杰西·詹姆士·賈瑞特所提出。
傳統(tǒng)的Web應(yīng)用允許用戶端填寫表單(form),當(dāng)提交表單時就向網(wǎng)頁服務(wù)器發(fā)送一個請求。服務(wù)器接收并處理傳來的表單,然后送回一個新的網(wǎng)頁,但這個做法浪費了許多帶寬,因為在前后兩個頁面中的大部分HTML碼往往是相同的。由于每次應(yīng)用的溝通都需要向服務(wù)器發(fā)送請求,應(yīng)用的回應(yīng)時間依賴于服務(wù)器的回應(yīng)時間。這導(dǎo)致了用戶界面的回應(yīng)比本機應(yīng)用慢得多。
與此不同,AJAX應(yīng)用可以僅向服務(wù)器發(fā)送并取回必須的數(shù)據(jù),并在客戶端采用JavaScript處理來自服務(wù)器的回應(yīng)。因為在服務(wù)器和瀏覽器之間交換的數(shù)據(jù)大量減少(大約只有原來的5%)[來源請求],服務(wù)器回應(yīng)更快了。同時,很多的處理工作可以在發(fā)出請求的客戶端機器上完成,因此Web服務(wù)器的負荷也減少了。
類似于DHTML或LAMP,AJAX不是指一種單一的技術(shù),而是有機地利用了一系列相關(guān)的技術(shù)。雖然其名稱包含XML,但實際上數(shù)據(jù)格式可以由JSON代替,進一步減少數(shù)據(jù)量,形成所謂的AJAJ。而客戶端與服務(wù)器也并不需要異步。一些基于AJAX的“派生/合成”式(derivative/composite)的技術(shù)也正在出現(xiàn),如AFLAX。 —— 維基百科
運用XHTML+CSS來表達信息;
運用JavaScript操作DOM(Document Object Model)來運行動態(tài)效果;
運用XML和XSLT操作數(shù)據(jù)
運用XMLHttpRequest或新的Fetch API與網(wǎng)頁服務(wù)器進行異步數(shù)據(jù)交換;
注意:AJAX與Flash、Silverlight和Java Applet等RIA技術(shù)是有區(qū)分的。
AJAX 兼容性JavaScript 編程的最大問題來自不同的瀏覽器對各種技術(shù)和標準的支持。
XmlHttpRequest 對象在不同瀏覽器中不同的創(chuàng)建方法,以下是跨瀏覽器的通用方法:
// Provide the XMLHttpRequest class for IE 5.x-6.x: // Other browsers (including IE 7.x-8.x) ignore this // when XMLHttpRequest is predefined var xmlHttp; if (typeof XMLHttpRequest != "undefined") { xmlHttp = new XMLHttpRequest(); } else if (window.ActiveXObject) { var aVersions = ["Msxml2.XMLHttp.5.0", "Msxml2.XMLHttp.4.0", "Msxml2.XMLHttp.3.0", "Msxml2.XMLHttp", "Microsoft.XMLHttp"]; for (var i = 0; i < aVersions.length; i++) { try { xmlHttp = new ActiveXObject(aVersions[i]); break; } catch (e) {} } }
詳細信息請參考 - Can I use XMLHttpRequest
AJAX/HTTP 庫對比Support | Features | |||||||
---|---|---|---|---|---|---|---|---|
All Browsers | Chrome & Firefox1 | Node | Concise Syntax | Promises | Native2 | Single Purpose3 | Formal Specification | |
XMLHttpRequest | ? | ? | ? | ? | ? | |||
Node HTTP | ? | ? | ? | ? | ||||
fetch() | ? | ? | ? | ? | ? | ? | ||
Fetch polyfill | ? | ? | ? | ? | ? | ? | ||
node-fetch | ? | ? | ? | ? | ? | |||
isomorphic-fetch | ? | ? | ? | ? | ? | ? | ? | |
superagent | ? | ? | ? | ? | ? | |||
axios | ? | ? | ? | ? | ? | ? | ||
request | ? | ? | ? | |||||
jQuery | ? | ? | ? | |||||
reqwest | ? | ? | ? | ? | ? | ? |
1 Chrome & Firefox are listed separately because they support fetch(): caniuse.com/fetch
2 Native: Meaning you can just use it - no need to include a library.
3 Single Purpose: Meaning this library or technology is ONLY used for AJAX / HTTP communication, nothing else.
詳細信息請參考 - AJAX/HTTP Library Comparison
XMLHTTP XMLHTTP 定義XMLHTTP 背景知識XMLHTTP 是一組API函數(shù)集,可被JavaScript、JScript、VBScript以及其它web瀏覽器內(nèi)嵌的腳本語言調(diào)用,通過HTTP在瀏覽器和web服務(wù)器之間收發(fā)XML或其它數(shù)據(jù)。XMLHTTP最大的好處在于可以動態(tài)地更新網(wǎng)頁,它無需重新從服務(wù)器讀取整個網(wǎng)頁,也不需要安裝額外的插件。該技術(shù)被許多網(wǎng)站使用,以實現(xiàn)快速響應(yīng)的動態(tài)網(wǎng)頁應(yīng)用。例如:Google的Gmail服務(wù)、Google Suggest動態(tài)查找界面以及Google Map地理信息服務(wù)。
XMLHTTP是AJAX網(wǎng)頁開發(fā)技術(shù)的重要組成部分。除XML之外,XMLHTTP還能用于獲取其它格式的數(shù)據(jù),如JSON或者甚至純文本?!?維基百科
XMLHTTP 實現(xiàn)XMLHTTP最初是由微軟公司發(fā)明的,在Internet Explorer?5.0中用作ActiveX對象,可通過JavaScript、VBScript或其它瀏覽器支持的腳本語言訪問。Mozilla的開發(fā)人員后來在Mozilla 1.0中實現(xiàn)了一個兼容的版本。之后蘋果電腦公司在Safari?1.2中開始支持XMLHTTP,而Opera從8.0版開始也宣布支持XMLHTTP。
大多數(shù)使用了XMLHTTP的設(shè)計良好的網(wǎng)頁,會使用簡單的JavaScript函數(shù),將不同瀏覽器之間調(diào)用XMLHTTP的差異性屏蔽,該函數(shù)會自動檢測瀏覽器版本并隱藏不同環(huán)境的差異。
在DOM?3(文檔對象模型 Level 3)的讀取和保存規(guī)范(Load and Save Specification)中也有類似的功能,它已經(jīng)成為W3C推薦的方法。截止2011年,大多數(shù)瀏覽器已經(jīng)支持?!?維基百科
ActiveXObject
XMLHttpRequest
什么是 ActiveX 控件Microsoft ActiveX 控件是由軟件提供商開發(fā)的可重用的軟件組件。使用 ActiveX 控件,可以很快地在網(wǎng)址、臺式應(yīng)用程序、以及開發(fā)工具中加入特殊的功能。例如,StockTicker 控件可以用來在網(wǎng)頁上即時地加入活動信息,動畫控件可用來向網(wǎng)頁中加入動畫特性。
ActiveXObject 對象JavaScript 中 ActiveXObject 對象是啟用并返回 Automation 對象的引用。
ActiveXObject 語法
newObj = new ActiveXObject(servername.typename[, location])
參數(shù):
newObj
必選 - ActiveXObject?分配到的變量名稱
servername
必選 - 提供對象的應(yīng)用程序名稱
typename
必選 - 要創(chuàng)建的對象的類型或類
location
可選 - 要再其中創(chuàng)建對象的網(wǎng)絡(luò)服務(wù)器的名稱
ActiveXObject 使用
// 在IE5.x和IE6下創(chuàng)建xmlHttp對象 // servername - MSXML2 // typename - XMLHTTP.3.0 var xmlHttp = new ActiveXObject("MSXML2.XMLHTTP.3.0"); xmlHttp.open("GET", "http://localhost/books.xml", false); xmlHttp.send();
詳細信息可以參考 - msdn - JavaScript 對象 - ActiveXObject 對象.aspx)
XMLHttpRequestXMLHttpRequest 是一個API, 它為客戶端提供了在客戶端和服務(wù)器之間傳輸數(shù)據(jù)的功能。它提供了一個通過 URL 來獲取數(shù)據(jù)的簡單方式,并且不會使整個頁面刷新。這使得網(wǎng)頁只更新一部分頁面而不會打擾到用戶。XMLHttpRequest 在?AJAX?中被大量使用。
XMLHttpRequest 是一個?JavaScript?對象,它最初由微軟設(shè)計,隨后被 Mozilla、Apple 和 Google采納. 如今,該對象已經(jīng)被?W3C組織標準化. 通過它,你可以很容易的取回一個URL上的資源數(shù)據(jù). 盡管名字里有XML, 但?XMLHttpRequest?可以取回所有類型的數(shù)據(jù)資源,并不局限于XML。 而且除了HTTP?,它還支持file?和?ftp?協(xié)議。
XMLHttpRequest 語法
var req = new XMLHttpRequest();
XMLHttpRequest 使用
var xhr = new XMLHttpRequest(); // 創(chuàng)建xhr對象 xhr.open( method, url ); xhr.onreadystatechange = function () { ... }; xhr.setRequestHeader( ..., ... ); xhr.send( optionalEncodedData );XMLHttpRequest 詳解 構(gòu)造函數(shù)
用于初始化一個 XMLHttpRequest 對象,必須在所有其它方法被調(diào)用前調(diào)用構(gòu)造函數(shù)。使用示例如下:
var req = new XMLHttpRequest();屬性
onreadystatechange: Function - 當(dāng) readyState 屬性改變時會調(diào)用它。
readyState: unsigned short - 用于表示請求的五種狀態(tài):
值 | 狀態(tài) | 描述 |
---|---|---|
0 | UNSENT (未打開) | 表示已創(chuàng)建 XHR 對象,open() 方法還未被調(diào)用 |
1 | OPENED (未發(fā)送) | open() 方法已被成功調(diào)用,send() 方法還未被調(diào)用 |
2 | HEADERS_RECEIVED (已獲取響應(yīng)頭) | send() 方法已經(jīng)被調(diào)用,響應(yīng)頭和響應(yīng)狀態(tài)已經(jīng)返回 |
3 | LOADING (正在下載響應(yīng)體) | 響應(yīng)體下載中,responseText中已經(jīng)獲取了部分數(shù)據(jù) |
4 | DONE (請求完成) | 整個請求過程已經(jīng)完畢 |
response: varies - 響應(yīng)體的類型由 responseType 來指定,可以是 ArrayBuffer、Blob、Document、JSON,或者是字符串。如果請求未完成或失敗,則該值為 null。
response: varies - 響應(yīng)體的類型由 responseType 來指定,可以是 ArrayBuffer、Blob、Document、JSON,或者是字符串。如果請求未完成或失敗,則該值為 null。
responseText: DOMString - 此請求的響應(yīng)為文本,或者當(dāng)請求未成功或還是未發(fā)送時未 null (只讀)
responseType: XMLHttpRequestResponseType - 設(shè)置該值能夠改變響應(yīng)類型,就是告訴服務(wù)器你期望的響應(yīng)格式:
值 | 響應(yīng)數(shù)據(jù)類型 |
---|---|
"" (空字符串) | 字符串(默認值) |
"arraybuffer" | ArrayBuffer |
"blob" | Blob |
"document" | Document |
"json" | JSON |
"text" | 字符串 |
xhr.spec 規(guī)范中定義的 XMLHttpRequestResponseType 類型如下:
enum XMLHttpRequestResponseType { "", "arraybuffer", "blob", "document", "json", "text" };
responseXML: Document - 本次請求響應(yīng)式一個 Document 對象,如果是以下情況則值為 null:
請求未成功
請求未發(fā)送
響應(yīng)無法被解析成 XML 或 HTML
status: unsigned short - 請求的響應(yīng)狀態(tài)碼,如 200 (表示一個成功的請求)。 (只讀)
statusText: DOMString - 請求的響應(yīng)狀態(tài)信息,包含一個狀態(tài)碼和消息文本,如 "200 OK"。 (只讀)
timeout: unsigned long - 表示一個請求在被自動終止前所消耗的毫秒數(shù)。默認值為 0,意味著沒有超時時間。超時并不能應(yīng)用在同步請求中,否則會拋出一個 InvalidAccessError 異常。當(dāng)發(fā)生超時時,timeout 事件將會被觸發(fā)。
upload: XMLHttpRequestUpload - 可以在 upload 上添加一個事件監(jiān)聽來跟蹤上傳過程
withCredentials: boolean - 表明在進行跨站 (cross-site) 的訪問控制 (Access-Control) 請求時,是否使用認證信息 (例如cookie或授權(quán)的header)。默認為 false。注意:這不會影響同站 same-site 請求
方法abort() - 如果請求已經(jīng)被發(fā)送,則立刻中止請求。
getAllResponseHeaders() - 返回所有響應(yīng)頭信息(響應(yīng)頭名和值),如果響應(yīng)頭還沒有接收,則返回 null。注意:使用該方法獲取的 response headers 與在開發(fā)者工具 Network 面板中看到的響應(yīng)頭不一致
getResponseHeader() - 返回指定響應(yīng)頭的值,如果響應(yīng)頭還沒有被接收,或該響應(yīng)頭不存在,則返回 null。注意:使用該方法獲取某些響應(yīng)頭時,瀏覽器會拋出異常,具體原因如下:
W3C的 xhr 標準中做了限制,規(guī)定客戶端無法獲取 response 中的 Set-Cookie、Set-Cookie2 這2個字段,無論是同域還是跨域請求。
W3C 的 cors 標準對于跨域請求也做了限制,規(guī)定對于跨域請求,客戶端允許獲取的response header字段只限于 simple response header (常見的 simple response header 如下)
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
和 Access-Control-Expose-Headers。
open() - 初始化一個請求:
方法簽名:
void open( DOMString method, DOMString url, optional boolean async, optional DOMString user, optional DOMString password );
參數(shù):
method - 請求所使用的 HTTP 方法,如 GET、POST、PUT、DELETE
url - 請求的 URL 地址
async - 一個可選的布爾值參數(shù),默認值為 true,表示執(zhí)行異步操作。如果值為 false,則 send() 方法不會返回任何東西,直到接收到了服務(wù)器的返回數(shù)據(jù)
user - 用戶名,可選參數(shù),用于授權(quán)。默認參數(shù)為空字符串
password - 密碼,可選參數(shù),用于授權(quán)。默認參數(shù)為空字符串
備注:
如果 method 不是有效的 HTTP 方法或 url 地址不能被成功解析,將會拋出 SyntaxError 異常
如果請求方法(不區(qū)分大小寫)為 CONNECT、TRACE 或 TRACK 將會拋出 SecurityError 異常
overrideMimeType() - 重寫由服務(wù)器返回的 MIME 類型。例如,可以用于強制把響應(yīng)流當(dāng)做 text/xml 來解析,即使服務(wù)器沒有指明數(shù)據(jù)是這個類型。注意:這個方法必須在 send() 之前被調(diào)用。
send() - 發(fā)送請求。如果該請求是異步模式(默認),該方法會立刻返回。相反,如果請求是同步模式,則直到請求的響應(yīng)完全接受以后,該方法才會返回。注意:所有相關(guān)的事件綁定必須在調(diào)用 send() 方法之前進行。
方法簽名:
void send(); void send(ArrayBuffer data); void send(Blob data); void send(Document data); void send(DOMString? data); void send(FormData data);
setRequestHeader() - 設(shè)置 HTTP 請求頭信息。注意:在這之前,你必須確認已經(jīng)調(diào)用了 open() 方法打開了一個 url
方法簽名:
void setRequestHeader( DOMString header, DOMString value );
參數(shù):
header - 請求頭名稱
value - 請求頭的值
sendAsBinary() - 發(fā)送二進制的 send() 方法的變種。
方法簽名:
void sendAsBinary( in DOMString body );
參數(shù):
body - 消息體
瀏覽器兼容性Desktop
Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari (WebKit) |
---|---|---|---|---|---|
Basic support (XHR1) | 1 | 1.0 | 5 (via ActiveXObject)7 (XMLHttpRequest) | (Yes) | 1.2 |
send(ArrayBuffer) | 9 | 9 | ? | 11.60 | ? |
send(Blob) | 7 | 3.6 | ? | 12 | ? |
send(FormData) | 6 | 4 | ? | 12 | ? |
response | 10 | 6 | 10 | 11.60 | ? |
responseType = "arraybuffer" | 10 | 6 | 10 | 11.60 | ? |
responseType = "blob" | 19 | 6 | 10 | 12 | ? |
responseType = "document" | 18 | 11 | 未實現(xiàn) | 未實現(xiàn) | 未實現(xiàn) |
responseType = "json" | 未實現(xiàn) | 10 | 未實現(xiàn) | 12 | 未實現(xiàn) |
Progress Events | 7 | 3.5 | 10 | 12 | ? |
withCredentials | 3 | 3.5 | 10 | 12 | 4 |
loadstart - 當(dāng)程序開始加載時,loadstart 事件將被觸發(fā)。
progress - 進度事件會被觸發(fā)用來指示一個操作正在進行中。
abort - 當(dāng)一個資源的加載已中止時,將觸發(fā)?abort 事件。
error - 當(dāng)一個資源加載失敗時會觸發(fā)error事件。
load - 當(dāng)一個資源及其依賴資源已完成加載時,將觸發(fā)load事件。
timeout - 當(dāng)進度由于預(yù)定時間到期而終止時,會觸發(fā)timeout?事件。
loadend - 當(dāng)一個資源加載進度停止時 (例如,在已經(jīng)分派“錯誤”,“中止”或“加載”之后),觸發(fā)loadend事件。
readystatechange - readystatechange 事件會在 document.readyState屬性發(fā)生變化時觸發(fā)。
XMLHttpRequest Level 1 XMLHttpRequest Level 1 使用首先,創(chuàng)建一個 XMLHttpRequest 對象:
var xhr = new XMLHttpRequest();
然后,向服務(wù)器發(fā)出一個 HTTP 請求:
xhr.open("GET", "example.php"); xhr.send();
接著,就等待遠程主機做出回應(yīng)。這時需要監(jiān)控XMLHttpRequest對象的狀態(tài)變化,指定回調(diào)函數(shù)。
xhr.onreadystatechange = function(){ if ( xhr.readyState == 4 && xhr.status == 200 ) { alert( xhr.responseText ); } else { alert( xhr.statusText ); } };
上面的代碼包含了老版本 XMLHttpRequest 對象的主要屬性:
xhr.readyState: XMLHttpRequest對象的狀態(tài),等于4表示數(shù)據(jù)已經(jīng)接收完畢。
xhr.status:服務(wù)器返回的狀態(tài)碼,等于200表示一切正常。
xhr.responseText:服務(wù)器返回的文本數(shù)據(jù)。
xhr.statusText:服務(wù)器返回的狀態(tài)文本。
XMLHttpRequest Level 1 缺點只支持文本數(shù)據(jù)的傳送,無法用來讀取和上傳二進制文件。
傳送和接收數(shù)據(jù)時,沒有進度信息,只能提示有沒有完成。
受到"同域限制"(Same Origin Policy),只能向同一域名的服務(wù)器請求數(shù)據(jù)。
XMLHttpRequest Level 2XMLHttpRequest Level 2 針對 XMLHttpRequest Level 1 的缺點,做了大幅改進。具體如下:
可以設(shè)置HTTP請求的超時時間。
可以使用FormData對象管理表單數(shù)據(jù)。
可以上傳文件。
可以請求不同域名下的數(shù)據(jù)(跨域請求)。
可以獲取服務(wù)器端的二進制數(shù)據(jù)。
可以獲得數(shù)據(jù)傳輸?shù)倪M度信息。
設(shè)置超時時間新版本 XMLHttpRequest 對象,增加了 timeout 屬性,可以設(shè)置HTTP請求的時限。
xhr.timeout = 3000;
上面的語句,將最長等待時間設(shè)為3000毫秒。過了這個時限,就自動停止HTTP請求。與之配套的還有一個timeout事件,用來指定回調(diào)函數(shù)。
xhr.ontimeout = function(event){ console.log("請求超時"); }FormData 對象
AJAX 操作往往用來傳遞表單數(shù)據(jù)。為了方便表單處理,HTML 5新增了一個 FormData 對象,可以用于模擬表單。
FormData 簡介構(gòu)造函數(shù) FormData()
用于創(chuàng)建一個新的 FormData 對象。
語法
var formData = new FormData(form)
參數(shù)
form 可選 - 一個 HTML 上的 表單元素。當(dāng)使用 form 參數(shù),創(chuàng)建的 FormData 對象會自動將 form 中的表單值也包含進去,文件內(nèi)容會被編碼
FormData 使用首先,新建一個 FormData 對象:
var formData = new FormData();
然后,為它添加表單項:
formData.append("username", "semlinker"); formData.append("id", 2005821040);
最后,直接傳送這個FormData對象。這與提交網(wǎng)頁表單的效果,完全一樣。
xhr.send(formData);
FormData 對象也可以用來獲取網(wǎng)頁表單的值。
var form = document.getElementById("myform"); // 獲取頁面上表單對象 var formData = new FormData(form); formData.append("username", "semlinker"); // 添加一個表單項 xhr.open("POST", form.action); xhr.send(formData);上傳文件
新版 XMLHttpRequest 對象,不僅可以發(fā)送文本信息,還可以上傳文件。
1.為了上傳文件, 我們得先選中一個文件. 一個 type 為 file 的 input 輸入框
2.然后用 FormData 對象包裹選中的文件
var input = document.getElementById("input"), formData = new FormData(); formData.append("file",input.files[0]); // file名稱與后臺接收的名稱一致
3.設(shè)置上傳地址和請求方法
var url = "http://localhost:3000/upload", method = "POST";
4.發(fā)送 FormData 對象
xhr.send(formData);跨域資源共享 (CORS)
新版本的 XMLHttpRequest 對象,可以向不同域名的服務(wù)器發(fā)出 HTTP 請求。這叫做 "跨域資源共享"(Cross-origin resource sharing,簡稱 CORS)。
使用"跨域資源共享"的前提,是瀏覽器必須支持這個功能,而且服務(wù)器端必須同意這種"跨域"。如果能夠滿足上面的條件,則代碼的寫法與不跨域的請求完全一樣。
xhr.open("GET", "http://other.server/and/path/to/script");接收二進制數(shù)據(jù)
XMLHttpRequest Level 1 XMLHttpRequest 對象只能處理文本數(shù)據(jù),新版則可以處理二進制數(shù)據(jù)。從服務(wù)器取回二進制數(shù)據(jù),較新的方法是使用新增的 responseType 屬性。如果服務(wù)器返回文本數(shù)據(jù),這個屬性的值是 "TEXT",這是默認值。較新的瀏覽器還支持其他值,也就是說,可以接收其他格式的數(shù)據(jù)。
你可以把 responseType 設(shè)為 blob,表示服務(wù)器傳回的是二進制對象。
var xhr = new XMLHttpRequest(); xhr.open("GET", "/path/to/image.png"); xhr.responseType = "blob"; xhr.send();
接收數(shù)據(jù)的時候,用瀏覽器自帶的 Blob 對象即可。
一個 ?Blob 對象表示一個不可變的, 原始數(shù)據(jù)的類似文件對象。Blob 表示的數(shù)據(jù)不一定是一個 JavaScript 原生格式。?File?接口基于Blob,繼承 blob功能并將其擴展為支持用戶系統(tǒng)上的文件。
var blob = new Blob([xhr.response], {type: "image/png"});
更多示例請參考?發(fā)送和接收二進制數(shù)據(jù)?。
進度信息新版本的 XMLHttpRequest 對象,傳送數(shù)據(jù)的時候,有一個 progress 事件,用來返回進度信息。
它分成上傳和下載兩種情況。下載的 progress 事件屬于 XMLHttpRequest 對象,上傳的 progress 事件屬于XMLHttpRequest.upload 對象。
我們先定義progress事件的回調(diào)函數(shù):
xhr.onprogress = updateProgress; xhr.upload.onprogress = updateProgress;
然后,在回調(diào)函數(shù)里面,使用這個事件的一些屬性。
function updateProgress(event) { if (event.lengthComputable) { var percentComplete = event.loaded / event.total; } }
上面的代碼中,event.total 是需要傳輸?shù)目傋止?jié),event.loaded 是已經(jīng)傳輸?shù)淖止?jié)。如果event.lengthComputable 不為真,則 event.total 等于0。
各個瀏覽器 XMLHttpRequest Level 2 的兼容性 - Can I use/xhr2
XHR 下載數(shù)據(jù)XHR 可以傳輸基于文本和二進制數(shù)據(jù)。實際上,瀏覽器可以為各種本地數(shù)據(jù)類型提供自動編碼和解碼,這樣可以讓應(yīng)用程序?qū)⑦@些類型直接傳遞給XHR,以便正確編碼,反之亦然,這些類型可以由瀏覽器自動解碼:
ArrayBuffer - 固定長度二進制數(shù)據(jù)緩沖區(qū)
Blob - 二進制不可變數(shù)據(jù)
Document - HTML或XML文檔
JSON - JavaScript Object Notation
Text - 普通文本
XHR 下載圖片示例:
var xhr = new XMLHttpRequest(); xhr.open("GET", "https://avatars2.githubusercontent.com/u/4220799?v=3"); xhr.responseType = "blob"; // 1 xhr.onload = function() { if (this.status == 200) { var img = document.createElement("img"); img.src = window.URL.createObjectURL(this.response); // 2 img.onload = function() { window.URL.revokeObjectURL(this.src); //3 }; document.body.appendChild(img); } }; xhr.send();
(1) 設(shè)置響應(yīng)的數(shù)據(jù)類型為 blob
(2) 基于Blob創(chuàng)建一個唯一的對象URL,并作為圖片的源地址 (URL.createObjectURL())
(3) 圖片加載成功后釋放對象的URL(URL.revokeObjectURL())
XHR 上傳數(shù)據(jù)通過 XHR 上傳數(shù)據(jù)對于所有數(shù)據(jù)類型來說都是簡單而有效的。實際上,唯一的區(qū)別是當(dāng)我們在XHR請求中調(diào)用 send() 時,我們需傳遞不同的數(shù)據(jù)對象。其余的由瀏覽器處理:
var xhr = new XMLHttpRequest(); xhr.open("POST","/upload"); xhr.onload = function() { ... }; xhr.send("text string"); // 1 var formData = new FormData(); // 2 formData.append("id", 123456); formData.append("topic", "performance"); var xhr = new XMLHttpRequest(); xhr.open("POST", "/upload"); xhr.onload = function() { ... }; xhr.send(formData); // 3 var xhr = new XMLHttpRequest(); xhr.open("POST", "/upload"); xhr.onload = function() { ... }; var uInt8Array = new Uint8Array([1, 2, 3]); // 4 xhr.send(uInt8Array.buffer); // 5
(1) 發(fā)送普通的文本到服務(wù)器
(2) 通過 FormData API 創(chuàng)建動態(tài)表單
(3) 發(fā)送 FormData 數(shù)據(jù)到服務(wù)器
(4) 創(chuàng)建 Unit8Array 數(shù)組 (Uint8Array 數(shù)組類型表示一個8位無符號整型數(shù)組,創(chuàng)建時內(nèi)容被初始化為0)
(5) 發(fā)送二進制數(shù)據(jù)到服務(wù)器
XHR send() 方法簽名:
void send(); void send(ArrayBuffer data); void send(Blob data); void send(Document data); void send(DOMString? data); void send(FormData data);
除此之外,XHR 還支持大文件分塊傳輸:
var blob = ...; // 1 const BYTES_PER_CHUNK = 1024 * 1024; // 2 const SIZE = blob.size; var start = 0; var end = BYTES_PER_CHUNK; while(start < SIZE) { // 3 var xhr = new XMLHttpRequest(); xhr.open("POST", "/upload"); xhr.onload = function() { ... }; xhr.setRequestHeader("Content-Range", start+"-"+end+"/"+SIZE); // 4 xhr.send(blob.slice(start, end)); // 5 start = end; end = start + BYTES_PER_CHUNK; }
(1) 一個任意的數(shù)據(jù)塊 (二進制或文本)
(2) 將數(shù)據(jù)庫大小設(shè)置為 1MB
(3) 迭代提供的數(shù)據(jù),增量為1MB
(4) 設(shè)置上傳的數(shù)據(jù)范圍 (Content-Range請求頭)
(5) 通過 XHR 上傳 1MB 數(shù)據(jù)塊
監(jiān)聽上傳和下載進度XHR 對象提供了一系列 API,用于監(jiān)聽進度事件,表示請求的當(dāng)前狀態(tài):
事件類型 | 描述 | 觸發(fā)次數(shù) |
---|---|---|
loadstart | 開始傳輸 | 1次 |
progress | 傳輸中 | 0次或多次 |
error | 傳輸中出現(xiàn)錯誤 | 0次或1次 |
abort | 傳輸被用戶取消 | 0次或1次 |
load | 傳輸成功 | 0次或1次 |
loadend | 傳輸完成 | 1次 |
每個 XHR 傳輸都以 loadstart 事件開始,并以 loadend 事件結(jié)束,并在這兩個事件期間觸發(fā)一個或多個附加事件來指示傳輸?shù)臓顟B(tài)。因此,為了監(jiān)控進度,應(yīng)用程序可以在 XHR 對象上注冊一組 JavaScript 事件偵聽器:
var xhr = new XMLHttpRequest(); xhr.open("GET","/resource"); xhr.timeout = 5000; // 1 xhr.addEventListener("load", function() { ... }); // 2 xhr.addEventListener("error", function() { ... }); // 3 var onProgressHandler = function(event) { if(event.lengthComputable) { var progress = (event.loaded / event.total) * 100; // 4 ... } } xhr.upload.addEventListener("progress", onProgressHandler); // 5 xhr.addEventListener("progress", onProgressHandler); // 6 xhr.send();
(1) 設(shè)置請求超時時間為 5,000 ms (默認無超時時間)
(2) 注冊成功回調(diào)
(3) 注冊異?;卣{(diào)
(4) 計算已完成的進度
(5) 注冊上傳進度事件回調(diào)
(6) 注冊下載進度事件回調(diào)
使用XHR流式傳輸數(shù)據(jù)在某些情況下,應(yīng)用程序可能需要或希望逐步處理數(shù)據(jù)流:將數(shù)據(jù)上傳到服務(wù)器,使其在客戶機上可用,或者在從服務(wù)器下載數(shù)據(jù)時,進行流式處理。
var xhr = new XMLHttpRequest(); xhr.open("GET", "/stream"); xhr.seenBytes = 0; xhr.onreadystatechange = function() { // 1 if(xhr.readyState > 2) { var newData = xhr.responseText.substr(xhr.seenBytes); // 2 // process newData xhr.seenBytes = xhr.responseText.length; // 3 } }; xhr.send();
(1) 監(jiān)聽 onreadystatechange 事件
(2) 從部分響應(yīng)中提取新數(shù)據(jù)
(3) 更新處理的字節(jié)偏移
這個例子可以在大多數(shù)現(xiàn)代瀏覽器中使用。但是,性能并不好,而且還有大量的注意事項和問題:
請注意,我們正在手動跟蹤所看到字節(jié)的偏移量,然后手動分割數(shù)據(jù):responseText 正在緩沖完整的響應(yīng)!對于小的傳輸,這可能不是一個問題,但對于更大的下載,特別是在內(nèi)存受限的設(shè)備,如手機,這是一個問題。釋放緩沖響應(yīng)的唯一方法是完成請求并打開一個新的請求。
部分響應(yīng)只能從 responseText 屬性中讀取,這將限制為僅限文本傳輸。沒有辦法讀取二進制傳輸?shù)牟糠猪憫?yīng)。
一旦讀取了部分數(shù)據(jù),我們必須識別消息邊界:應(yīng)用程序邏輯必須定義自己的數(shù)據(jù)格式,然后緩沖并解析流以提取單個消息。
瀏覽器在處理緩沖數(shù)據(jù)方面有所不同:一些瀏覽器可能會立即釋放數(shù)據(jù),而其他瀏覽器可能會緩沖小的響應(yīng)并等到積累到一定大小的數(shù)據(jù)塊才釋放它們。
瀏覽器對不同 Content-Type 資源類型的處理方式不同,對于某些資源類型允許逐步讀取 - 例如,text / html 類型,而其他 Content-Type 類型只能使用 application / x-javascript。
XHR 定時輪詢從服務(wù)器檢索更新的最簡單的策略之一是讓客戶端進行定期檢查:客戶端可以以周期性間隔(輪詢服務(wù)器)啟動后臺XHR請求,以檢查更新。如果新數(shù)據(jù)在服務(wù)器上可用,則在響應(yīng)中返回,否則響應(yīng)為空。
定時輪詢的方式很簡單,但如果定時間隔很短的話,也是很低效。因此設(shè)置合適的時間間隔顯得至關(guān)重要:輪詢間隔時間過長,會導(dǎo)致更新不及時,然而間隔時間過短的話,則會導(dǎo)致客戶端與服務(wù)器不必要的流程和高開銷。接下來我們來看一個簡單的示例:
function checkUpdates(url) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function() { ... }; // 1 xhr.send(); } setInterval(function() { checkUpdates("/updates") }, 60000); // 2
(1) 處理服務(wù)端接收的數(shù)據(jù)
(2) 設(shè)置定時輪詢時間為 60s
定時輪詢會產(chǎn)生以下的問題:
每個 XHR 請求都是一個獨立的 HTTP 請求,平均來說,HTTP 的請求頭可能會引起大約 800 字節(jié)的開銷 (不帶HTTP cookie)。
每個瀏覽器發(fā)起 HTTP 請求時都將攜帶額外的 500 - 800 字節(jié)的元數(shù)據(jù) (請求頭),如 user-agent、accept、Cache-Control 緩存控制頭等。更糟糕的是,500 - 800 字節(jié)是理想的情況,如果攜帶 Cookies 信息,那么這個數(shù)值將會更大。總而言之,這些未壓縮的 HTTP 元數(shù)據(jù)會引起很大開銷。
如果數(shù)據(jù)能夠在間隔期間順序到達,那么定時輪詢可以正常工作。但我們并沒有任何機制保證數(shù)據(jù)的正常接收。另外周期性輪詢也將會引起服務(wù)器上可用的消息及其傳送到客戶端之間引入額外的延遲。簡單的理解是如果有輪詢期間有新的可用消息,客戶端是不會馬上收到此新消息,而是要等到下一次輪詢的時候,才能獲取最新數(shù)據(jù)。
除非仔細考慮,不然輪詢通常會成為無線網(wǎng)絡(luò)上昂貴的性能反模式。頻繁地輪詢會大量的消耗移動設(shè)備的電量。
輪詢開銷平均每個 HTTP 1.x 請求會增加大約 800字節(jié)的請求和響應(yīng)開銷 (詳細信息可以查看 - Measuring and Controlling Protocol Overhead) 。另外在客戶端登錄后,我們還將產(chǎn)生一個額外的身份驗證 cookie 和 消息ID; 假設(shè)這又增加了50個字節(jié)。因此,不返回新消息的請求將產(chǎn)生 850字節(jié)開銷!現(xiàn)在假設(shè)我們有10,000個客戶端,所有的輪詢間隔時間都是60秒:
$$
(850 bytes 8 bits 10,000) / 60 seconds ≈ 1.13 Mbps
$$
每個客戶端在每個請求上發(fā)送 850 字節(jié)的數(shù)據(jù),這轉(zhuǎn)換為每秒 167 個請求,服務(wù)器上的吞吐量大約為 1.13 Mbps!這不是一個固定的值,此外該計算值還是在假設(shè)服務(wù)器沒有向任何客戶端傳遞任何新的消息的理想情況下計算而得的。
周期性輪詢的挑戰(zhàn)在于有可能進行許多不必要的和空的檢查??紤]到這一點,如果我們對輪詢工作流程進行了輕微的修改,而不是在沒有更新可用的情況下返回一個空的響應(yīng),我們可以保持連接空閑,直到更新可用嗎?
(圖片來源 - https://hpbn.co/xmlhttprequest/)
通過保持長連接,直到更新可用,數(shù)據(jù)可以立即發(fā)送到客戶端,一旦它在服務(wù)器上可用。因此,長時間輪詢?yōu)橄⒀舆t提供了最佳的情況,并且還消除了空檢查,這減少了 XHR 請求的數(shù)量和輪詢的總體開銷。一旦更新被傳遞,長的輪詢請求完成,并且客戶端可以發(fā)出另一個長輪詢請求并等待下一個可用的消息:
function checkUpdates(url) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function() { // 1 ... checkUpdates("/updates"); // 2 }; xhr.send(); } checkUpdates("/updates"); // 3
(1) 處理接收到的數(shù)據(jù)并啟動下一輪檢測更新
(2) 啟動下一輪檢測更新
(3) 發(fā)起首次更新請求
那么長時間輪詢總是比定期輪詢更好的選擇?除非消息到達率已知且不變,否則長輪詢將始終提供更短的消息延遲。
另一方面,開銷討論需要更細微的觀點。首先,請注意,每個傳遞的消息仍然引起相同的 HTTP 開銷;每個新消息都是獨立的 HTTP 請求。但是,如果消息到達率高,那么長時間輪詢會比定期輪詢發(fā)出更多的XHR請求!
長輪詢通過最小化消息延遲來動態(tài)地適應(yīng)消息到達速率,這是您可能想要的或可能不需要的行為。如果對消息延遲要求不高的話,則定時輪詢可能是更有效的傳輸方式 - 例如,如果消息更新速率較高,則定時輪詢提供簡單的 "消息聚合" 機制 (即合并一定時間內(nèi)的消息),這可以減少請求數(shù)量并提高移動設(shè)備的電池壽命。
XMLHttpRequest 庫 Mock.jsMock.js 是一款模擬數(shù)據(jù)生成器,旨在幫助前端攻城師獨立于后端進行開發(fā),幫助編寫單元測試。提供了以下模擬功能:
根據(jù)數(shù)據(jù)模板生成模擬數(shù)據(jù)
模擬 Ajax 請求,生成并返回模擬數(shù)據(jù)
基于 HTML 模板生成模擬數(shù)據(jù)
詳細信息,請查看 - Mock.js 文檔
Zone.jsZone 是下一個 ECMAScript 規(guī)范的建議之一。Angular 團隊實現(xiàn)了 JavaScript 版本的 zone.js ,它是用于攔截和跟蹤異步工作的機制。
Zone 是一個全局的對象,用來配置有關(guān)如何攔截和跟蹤異步回調(diào)的規(guī)則。Zone 有以下能力:
攔截異步任務(wù)調(diào)度,如 setTimeout、setInterval、XMLHttpRequest 等
提供了將數(shù)據(jù)附加到 zones 的方法
為異常處理函數(shù)提供正確的上下文
攔截阻塞的方法,如 alert、confirm 方法
zone.js 內(nèi)部使用 Monkey Patch 方式,攔截 XMLHttpRequest.prototype 對象中的 open、send、abort 等方法。
// zone.js 源碼片段 var openNative = patchMethod(window.XMLHttpRequest.prototype, "open", function () { return function (self, args) { self[XHR_SYNC] = args[2] == false; return openNative.apply(self, args); }; });Oboe.js
Oboe.js 通過將 HTTP 請求-應(yīng)答模型封裝在一個漸進流式接口中,幫助網(wǎng)頁應(yīng)用快速應(yīng)答。它將 streaming 和downloading 間的轉(zhuǎn)換與SAX和DOM間JSON的解析整合在一起。它是個非常小的庫,不依賴于其他程序庫。它可以在 ajax 請求結(jié)束前就開始解析 json 變得十分容易,從而提高應(yīng)用的應(yīng)答速度。另外,它支持 Node.js 框架,還可以讀入除了 http 外的其他流。
有興趣的讀者,推薦看一下官網(wǎng)的可交互的演示示例 - Why Oboe.js
(備注:該庫就是文中 - 使用XHR流式傳輸數(shù)據(jù)章節(jié)的實際應(yīng)用,不信往下看)
// oboe-browser.js 源碼片段 function handleProgress() { var textSoFar = xhr.responseText, newText = textSoFar.substr(numberOfCharsAlreadyGivenToCallback); if( newText ) { emitStreamData( newText ); } numberOfCharsAlreadyGivenToCallback = len(textSoFar); }fetch.js
fetch 函數(shù)是一個基于 Promise 的機制,用于在瀏覽器中以編程方式發(fā)送 Web 請求。該項目是實現(xiàn)標準 Fetch 規(guī)范的一個子集的 polyfill ,足以作為傳統(tǒng) Web 應(yīng)用程序中 XMLHttpRequest 的代替品。
詳細信息,請參考 - Github - fetch
Fetch API 兼容性,請參考 - Can I use Fetch
XMLHttpRequest 代碼片段 ArrayBuffer 對象轉(zhuǎn)為字符串function ab2str(buf) { return String.fromCharCode.apply(null, new Uint16Array(buf)); }
代碼片段來源 - ArrayBuffer與字符串的互相轉(zhuǎn)換
字符串轉(zhuǎn) ArrayBuffer對象function str2ab(str) { var buf = new ArrayBuffer(str.length * 2); // 每個字符占用2個字節(jié) var bufView = new Uint16Array(buf); for (var i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; }
代碼片段來源 - ArrayBuffer與字符串的互相轉(zhuǎn)換
創(chuàng)建 XHR 對象 兼容所有瀏覽器// Provide the XMLHttpRequest class for IE 5.x-6.x: // Other browsers (including IE 7.x-8.x) ignore this // when XMLHttpRequest is predefined var xmlHttp; if (typeof XMLHttpRequest != "undefined") { xmlHttp = new XMLHttpRequest(); } else if (window.ActiveXObject) { var aVersions = ["Msxml2.XMLHttp.5.0", "Msxml2.XMLHttp.4.0", "Msxml2.XMLHttp.3.0", "Msxml2.XMLHttp", "Microsoft.XMLHttp"]; for (var i = 0; i < aVersions.length; i++) { try { xmlHttp = new ActiveXObject(aVersions[i]); break; } catch (e) {} } }精簡版
var xmlHttp; if (typeof XMLHttpRequest != "undefined") { xmlHttp = new XMLHttpRequest(); } else if (window.ActiveXObject) { try { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {} }sendAsBinary() polyfill
if (!XMLHttpRequest.prototype.sendAsBinary) { XMLHttpRequest.prototype.sendAsBinary = function (sData) { var nBytes = sData.length, ui8Data = new Uint8Array(nBytes); for (var nIdx = 0; nIdx < nBytes; nIdx++) { ui8Data[nIdx] = sData.charCodeAt(nIdx) & 0xff; } this.send(ui8Data); }; }獲取 XMLHttpRequest 響應(yīng)體
function readBody(xhr) { var data; if (!xhr.responseType || xhr.responseType === "text") { data = xhr.responseText; } else if (xhr.responseType === "document") { data = xhr.responseXML; } else { data = xhr.response; } return data; }
應(yīng)用示例:
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { console.log(readBody(xhr)); } } xhr.open("GET", "https://www.baidu.com", true); xhr.send(null);獲取 responseURL
export function getResponseURL(xhr: any): string { if ("responseURL" in xhr) { return xhr.responseURL; } if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { return xhr.getResponseHeader("X-Request-URL"); } return; }
代碼片段來源 - Github - @angular/http - http_utils.ts
驗證請求是否成功export const isSuccess = (status: number): boolean => (status >= 200 && status < 300);
代碼片段來源 - Github - @angular/http - http_utils.ts
解析查詢參數(shù)為Map對象function paramParser(rawParams: string = ""): Map{ const map = new Map (); if (rawParams.length > 0) { const params: string[] = rawParams.split("&"); params.forEach((param: string) => { const eqIdx = param.indexOf("="); const [key, val]: string[] = eqIdx == -1 ? [param, ""] : [param.slice(0, eqIdx), param.slice(eqIdx + 1)]; const list = map.get(key) || []; list.push(val); map.set(key, list); }); } return map; }
代碼片段來源 - Github - @angular/http - url_search_params.ts
ts 轉(zhuǎn)換為 js 的代碼如下:
function paramParser(rawParams) { if (rawParams === void 0) { rawParams = ""; } var map = new Map(); if (rawParams.length > 0) { var params = rawParams.split("&"); params.forEach(function (param) { var eqIdx = param.indexOf("="); var _a = eqIdx == -1 ? [param, ""] : [param.slice(0, eqIdx), param.slice(eqIdx + 1)], key = _a[0], val = _a[1]; var list = map.get(key) || []; list.push(val); map.set(key, list); }); } return map; }XHR 下載圖片
var xhr = new XMLHttpRequest(); xhr.open("GET", "https://avatars2.githubusercontent.com/u/4220799?v=3"); xhr.responseType = "blob"; xhr.onload = function() { if (this.status == 200) { var img = document.createElement("img"); img.src = window.URL.createObjectURL(this.response); img.onload = function() { window.URL.revokeObjectURL(this.src); }; document.body.appendChild(img); } }; xhr.send();XHR 上傳數(shù)據(jù) 發(fā)送普通文本
var xhr = new XMLHttpRequest(); xhr.open("POST","/upload"); xhr.onload = function() { ... }; xhr.send("text string");發(fā)送FormData
var formData = new FormData(); formData.append("id", 123456); formData.append("topic", "performance"); var xhr = new XMLHttpRequest(); xhr.open("POST", "/upload"); xhr.onload = function() { ... }; xhr.send(formData);發(fā)送 Buffer
var xhr = new XMLHttpRequest(); xhr.open("POST", "/upload"); xhr.onload = function() { ... }; var uInt8Array = new Uint8Array([1, 2, 3]); xhr.send(uInt8Array.buffer);XHR 上傳進度條
progress 元素
定義 progress 事件的回調(diào)函數(shù)
xhr.upload.onprogress = function (event) { if (event.lengthComputable) { var complete = (event.loaded / event.total * 100 | 0); var progress = document.getElementById("uploadprogress"); progress.value = progress.innerHTML = complete; } };
注意,progress事件不是定義在xhr,而是定義在xhr.upload,因為這里需要區(qū)分下載和上傳,下載也有一個progress事件。
我有話說 1.什么情況下 XMLHttpRequest status 會為 0?XMLHttpRequest 返回 status 時,會執(zhí)行以下步驟:
如果狀態(tài)是 UNSENT 或 OPENED,則返回 0
如果錯誤標志被設(shè)置,則返回 0
否則返回 HTTP 狀態(tài)碼
另外當(dāng)訪問本地文件資源或在 Android 4.1 stock browser 中從應(yīng)用緩存中獲取文件時,XMLHttpRequest 的 status 值也會為0。
示例一:
var xmlhttp; xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET","http://www.w3schools.com/XML/cd_catalog.xml", true); xmlhttp.onreadystatechange=function() { if(xmlhttp.readyState == 4) console.log("status " + xmlhttp.status); }; xmlhttp.addEventListener("error", function (error) { console.dir(error); }); xmlhttp.send();
以上代碼運行后,將會在控制臺輸出:
status 0 ProgressEvent # error 對象2.為什么 GET 或 HEAD 請求,不能通過 send() 方法發(fā)送請求體?
client . send([body = null])
Initiates the request. The optional argument provides the request body. The argument is ignored if request method is GET or HEAD. —— xhr.spec
通過 XMLHttpRequest 規(guī)范,我們知道當(dāng)請求方法是 GET 或 HEAD 時,send() 方法的 body 參數(shù)值將會被忽略。那么對于我們常用的 GET 請求,我們要怎么傳遞參數(shù)呢?解決參數(shù)傳遞可以使用以下兩種方式:
URL 傳參 - 常用方式,有大小限制大約為 2KB
請求頭傳參 - 一般用于傳遞 token 等認證信息
URL 傳參
var url = "bla.php"; var params = "somevariable=somevalue&anothervariable=anothervalue"; var http = new XMLHttpRequest(); http.open("GET", url+"?"+params, true); http.onreadystatechange = function() { if(http.readyState == 4 && http.status == 200) { alert(http.responseText); } } http.send(null); // 請求方法是GET或HEAD時,設(shè)置請求體為空
在日常開發(fā)中,我們最常用的方式是傳遞參數(shù)對象,因此我們可以封裝一個 formatParams() 來實現(xiàn)參數(shù)格式,具體示例如下:
formatParams() 函數(shù):
function formatParams( params ){ return "?" + Object .keys(params) .map(function(key){ return key+"="+params[key] }) .join("&") }
應(yīng)用示例:
var endpoint = "https://api.example.com/endpoint"; var params = { a: 1, b: 2, c: 3 }; var url = endpoint + formatParams(params); // 實際應(yīng)用中需要判斷endpoint是否已經(jīng)包含查詢參數(shù) // => "https://api.example.com/endpoint?a=1&b=2&c=3";
一些常用的 AJAX 庫,如 jQuery、zepto 等,內(nèi)部已經(jīng)封裝了參數(shù)序列化的方法 (如:jquery.param),我們直接調(diào)用頂層的 API 方法即可。
(備注:以上示例來源 - stackoverflow - How do I pass along variables with XMLHttpRequest)
請求頭傳參 - (身份認證)
var xhr = new XMLHttpRequest(); xhr.open("POST", "/server", true); xhr.setRequestHeader("x-access-token", "87a476494db6ec53d0a206589611aa3f"); xhr.onreadystatechange = function() { if(xhr.readyState == 4 && xhr.status == 200) { // handle data } }; xhr.send("foo=bar&lorem=ipsum");
詳細的身份認證信息,請參考 - JSON Web Tokens
3.XMLHttpRequest 請求體支持哪些格式?send() 方法簽名:
void send(); void send(ArrayBuffer data); void send(Blob data); void send(Document data); void send(DOMString? data); void send(FormData data);
POST請求示例
發(fā)送 POST 請求通常需要以下步驟:
使用 open() 方法打開連接時,設(shè)定 POST 請求方法和請求 URL地址
設(shè)置正確的 Content-Type 請求頭
設(shè)置相關(guān)的事件監(jiān)聽
設(shè)置請求體,并使用 send() 方法,發(fā)送請求
var xhr = new XMLHttpRequest(); xhr.open("POST", "/server", true); //Send the proper header information along with the request xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if(xhr.readyState == 4 && xhr.status == 200) { // handle data } } xhr.send("foo=bar&lorem=ipsum"); // xhr.send("string"); // xhr.send(new Blob()); // xhr.send(new Int8Array()); // xhr.send({ form: "data" }); // xhr.send(document);4.什么是簡單請求和預(yù)請求 (preflight request) ?
簡單請求
一些不會觸發(fā) CORS preflight 的請求被稱為 "簡單請求",雖然 Fetch (定義 CORS的) 不使用這個術(shù)語。滿足下述條件的就是 "簡單請求":
只允許下列方法:
GET
HEAD
POST
除了用戶代理自動設(shè)置的頭部外(比如 Connection, User-Agent ,或者其他任意的 Fetch 規(guī)范定義的 禁止的頭部名 ),唯一允許人工設(shè)置的頭部是 Fetch 規(guī)范定義的 CORS-safelisted request-header,如下:
Accept
Accept-Language
Content-Language
Content-Type
允許的 Content-Type 值有:
application/x-www-form-urlencoded
multipart/form-data
text/plain
預(yù)請求
不同于上面討論的簡單請求,"預(yù)請求" 要求必須先發(fā)送一個 OPTIONS 方法請求給目的站點,來查明這個跨站請求對于目的站點是不是安全的可接受的。這樣做,是因為跨站請求可能會對目的站點的數(shù)據(jù)產(chǎn)生影響。 當(dāng)請求具備以下條件,就會被當(dāng)成預(yù)請求處理:
使用下述方法以外的請求:
GET
HEAD
POST
除了用戶代理自動設(shè)置的頭部外(比如 Connection, User-Agent ,或者其他任意的 Fetch 規(guī)范定義的 “禁止的頭部名” ),預(yù)請求不包括 Fetch 規(guī)范定義的 "CORS-safelisted request-header":
Accept
Accept-Language
Content-Language
Content-Type
Content-Type 頭部的值除了下列之外的:
application/x-www-form-urlencoded
multipart/form-data
text/plain
詳細的信息,請參考 - MDN - HTTP 訪問控制 (CORS)
5.XMLHttpRequest 對象垃圾回收機制是什么?在以下情況下,XMLHttpRequest 對象不會被垃圾回收:
如果 XMLHttpRequest 對象的狀態(tài)是 OPENED 且已設(shè)置 send() 的標識符
XMLHttpRequest 對象的狀態(tài)是 HEADERS_RECEIVED (已獲取響應(yīng)頭)
XMLHttpRequest 對象的狀態(tài)是 LOADING (正在下載響應(yīng)體),并且監(jiān)聽了以下一個或多個事件:readystatechange、progress、abort、error、load、timeout、loadend
如果 XMLHttpRequest 對象在連接尚存打開時被垃圾回收機制回收了,用戶代理必須終止請求。
6.GET 和 POST 請求的區(qū)別?對于 GET 請求,瀏覽器會把 HTTP headers 和 data 一并發(fā)送出去,服務(wù)器響應(yīng) 200。
而對于 POST 請求,瀏覽器會先發(fā)送 HTTP headers,服務(wù)器響應(yīng) 100 continue ,瀏覽器再發(fā)送 data,服務(wù)器響應(yīng) 200。
詳細的信息,請參考 - 99%的人都理解錯了HTTP中GET與POST的區(qū)別
7.怎樣防止重復(fù)發(fā)送 AJAX 請求?setTimeout + clearTimeout - 連續(xù)的點擊會把上一次點擊清除掉,也就是ajax請求會在最后一次點擊后發(fā)出去
disable 按鈕
緩存已成功的請求,若請求參數(shù)一致,則直接返回,不發(fā)送請求
詳細的信息,請參考 - 知乎 - 怎樣防止重復(fù)發(fā)送 Ajax 請求
8、AJAX 站點怎么做 SEO 優(yōu)化眾所周知,大部分的搜索引擎爬蟲都不會執(zhí)行 JS,也就是說,如果頁面內(nèi)容由 Ajax 返回的話,搜索引擎是爬取不到部分內(nèi)容的,也就無從做 SEO (搜索引擎優(yōu)化)了。國外的 prerender.io 網(wǎng)站提供了一套比較成熟的方案,但是需要付費的。接下來我們來看一下,怎么 PhantomJS 為我們的站點做 SEO。
詳細的信息,請參考 - 用PhantomJS來給AJAX站點做SEO優(yōu)化
精品文章XMLHttpRequest 2新技巧
阮一峰 - XMLHttpRequest Level 2 使用指南
segmentfault - 你真的會使用XMLHttpRequest嗎?
Github - Ajax 知識體系大梳理
參考資源XMLHttpRequest 標準
High Performance Browser Networking - XMLHttpRequest
msdn - ActiveXObject 對象.aspx)
mdn - XMLHttpRequest
XMLHttpRequest 2新技巧
阮一峰 - XMLHttpRequest Level 2 使用指南
segmentfault - 你真的會使用XMLHttpRequest嗎?
Github - Ajax 知識體系大梳理
JavaScript 標準參考教程 (alpha) - 二進制數(shù)組?
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/82364.html
摘要:同源策略禁止使用對象向不同源的服務(wù)器地址發(fā)起請求。借助于決解同源策略決解同源策略,新方案跨域資源共享這里講的重點跨域資源共享提供的標準跨域解決方案,是一個由瀏覽器共同遵循的一套控制策略,通過的來進行交互主要通過后端來設(shè)置配置項。 了解下同源策略 源(origin)*:就是協(xié)議、域名和端口號; 同源: 就是源相同,即協(xié)議、域名和端口完全相同; 同源策略:同源策略是瀏覽器的一個安全...
摘要:異步請求線程在在連接后是通過瀏覽器新開一個線程請求將檢測到狀態(tài)變更時,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個回調(diào)再放入事件循環(huán)隊列中。 基礎(chǔ):瀏覽器 -- 多進程,每個tab頁獨立一個瀏覽器渲染進程(瀏覽器內(nèi)核) 每個瀏覽器渲染進程是多線程的,主要包括:GUI渲染線程 JS引擎線程 也稱為JS內(nèi)核,負責(zé)處理Javascript腳本程序。(例如V8引擎) JS引擎線程負...
摘要:在本文中,我們將介紹一些在中發(fā)出請求的流行方法。是發(fā)出異步請求的傳統(tǒng)方式。如果等于,則表示請求已完成。是進行調(diào)用的最簡單方法之一。它需要三個參數(shù)請求的地址您要發(fā)送的數(shù)據(jù)和回調(diào)函數(shù)。事實上,這是制作請求的最佳和最喜歡的方式之一。 showImg(https://segmentfault.com/img/bVbdEhE?w=749&h=450);JavaScript具有很好的模塊和方法來發(fā)...
閱讀 2491·2021-11-23 09:51
閱讀 1294·2021-11-22 13:52
閱讀 3704·2021-11-10 11:35
閱讀 1339·2021-10-25 09:47
閱讀 3119·2021-09-07 09:58
閱讀 1143·2019-08-30 15:54
閱讀 2916·2019-08-29 14:21
閱讀 3119·2019-08-29 12:20