本文詳細介紹了 XMLHttpRequest 相關知識,涉及內容:javascript
AJAX即「Asynchronous JavaScript and XML」(異步的JavaScript與XML技術),指的是一套綜合了多項技術的瀏覽器端網頁開發技術。Ajax的概念由傑西·詹姆士·賈瑞特所提出。 php
傳統的Web應用容許用戶端填寫表單(form),當提交表單時就向網頁服務器發送一個請求。服務器接收並處理傳來的表單,而後送回一個新的網頁,但這個作法浪費了許多帶寬,由於在先後兩個頁面中的大部分HTML碼每每是相同的。因爲每次應用的溝通都須要向服務器發送請求,應用的迴應時間依賴於服務器的迴應時間。這致使了用戶界面的迴應比本機應用慢得多。html
與此不一樣,AJAX應用能夠僅向服務器發送並取回必須的數據,並在客戶端採用JavaScript處理來自服務器的迴應。由於在服務器和瀏覽器之間交換的數據大量減小(大約只有原來的5%)[來源請求],服務器迴應更快了。同時,不少的處理工做能夠在發出請求的客戶端機器上完成,所以Web服務器的負荷也減小了。前端
相似於DHTML或LAMP,AJAX不是指一種單一的技術,而是有機地利用了一系列相關的技術。雖然其名稱包含XML,但實際上數據格式能夠由JSON代替,進一步減小數據量,造成所謂的AJAJ。而客戶端與服務器也並不須要異步。一些基於AJAX的「派生/合成」式(derivative/composite)的技術也正在出現,如AFLAX。 —— 維基百科html5
JavaScript 編程的最大問題來自不一樣的瀏覽器對各類技術和標準的支持。java
XmlHttpRequest 對象在不一樣瀏覽器中不一樣的建立方法,如下是跨瀏覽器的通用方法:node
// 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 XMLHttpRequestjquery
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.ios
詳細信息請參考 - AJAX/HTTP Library Comparisongit
XMLHTTP 是一組API函數集,可被JavaScript、JScript、VBScript以及其它web瀏覽器內嵌的腳本語言調用,經過HTTP在瀏覽器和web服務器之間收發XML或其它數據。XMLHTTP最大的好處在於能夠動態地更新網頁,它無需從新從服務器讀取整個網頁,也不須要安裝額外的插件。該技術被許多網站使用,以實現快速響應的動態網頁應用。例如:Google的Gmail服務、Google Suggest動態查找界面以及Google Map地理信息服務。
XMLHTTP是AJAX網頁開發技術的重要組成部分。除XML以外,XMLHTTP還能用於獲取其它格式的數據,如JSON或者甚至純文本。—— 維基百科
XMLHTTP最初是由微軟公司發明的,在Internet Explorer 5.0中用做ActiveX對象,可經過JavaScript、VBScript或其它瀏覽器支持的腳本語言訪問。Mozilla的開發人員後來在Mozilla 1.0中實現了一個兼容的版本。以後蘋果電腦公司在Safari 1.2中開始支持XMLHTTP,而Opera從8.0版開始也宣佈支持XMLHTTP。
大多數使用了XMLHTTP的設計良好的網頁,會使用簡單的JavaScript函數,將不一樣瀏覽器之間調用XMLHTTP的差別性屏蔽,該函數會自動檢測瀏覽器版本並隱藏不一樣環境的差別。
在DOM 3(文檔對象模型 Level 3)的讀取和保存規範(Load and Save Specification)中也有相似的功能,它已經成爲W3C推薦的方法。截止2011年,大多數瀏覽器已經支持。—— 維基百科
Microsoft ActiveX 控件是由軟件提供商開發的可重用的軟件組件。使用 ActiveX 控件,能夠很快地在網址、臺式應用程序、以及開發工具中加入特殊的功能。例如,StockTicker 控件能夠用來在網頁上即時地加入活動信息,動畫控件可用來向網頁中加入動畫特性。
JavaScript 中 ActiveXObject 對象是啓用並返回 Automation 對象的引用。
ActiveXObject 語法
newObj = new ActiveXObject(servername.typename[, location])複製代碼
參數:
ActiveXObject 使用
// 在IE5.x和IE6下建立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)
XMLHttpRequest 是一個API, 它爲客戶端提供了在客戶端和服務器之間傳輸數據的功能。它提供了一個經過 URL 來獲取數據的簡單方式,而且不會使整個頁面刷新。這使得網頁只更新一部分頁面而不會打擾到用戶。XMLHttpRequest 在 AJAX 中被大量使用。
XMLHttpRequest 是一個 JavaScript 對象,它最初由微軟設計,隨後被 Mozilla、Apple 和 Google採納. 現在,該對象已經被 W3C組織標準化. 經過它,你能夠很容易的取回一個URL上的資源數據. 儘管名字裏有XML, 但 XMLHttpRequest 能夠取回全部類型的數據資源,並不侷限於XML。 並且除了HTTP ,它還支持file
和 ftp
協議。
XMLHttpRequest 語法
var req = new XMLHttpRequest();複製代碼
XMLHttpRequest 使用
var xhr = new XMLHttpRequest(); // 建立xhr對象
xhr.open( method, url );
xhr.onreadystatechange = function () { ... };
xhr.setRequestHeader( ..., ... );
xhr.send( optionalEncodedData );複製代碼
用於初始化一個 XMLHttpRequest 對象,必須在全部其它方法被調用前調用構造函數。使用示例以下:
var req = new XMLHttpRequest();複製代碼
值 | 狀態 | 描述 |
---|---|---|
0 | UNSENT (未打開) | 表示已建立 XHR 對象,open() 方法還未被調用 |
1 | OPENED (未發送) | open() 方法已被成功調用,send() 方法還未被調用 |
2 | HEADERS_RECEIVED (已獲取響應頭) | send() 方法已經被調用,響應頭和響應狀態已經返回 |
3 | LOADING (正在下載響應體) | 響應體下載中,responseText中已經獲取了部分數據 |
4 | DONE (請求完成) | 整個請求過程已經完畢 |
值 | 響應數據類型 |
---|---|
"" | 字符串(默認值) |
"arraybuffer" | ArrayBuffer |
"blob" | Blob |
"document" | Document |
"json" | JSON |
"text" | 字符串 |
xhr.spec 規範中定義的 XMLHttpRequestResponseType 類型以下:
enum XMLHttpRequestResponseType {
"",
"arraybuffer",
"blob",
"document",
"json",
"text"
};複製代碼
abort() - 若是請求已經被髮送,則馬上停止請求。
getAllResponseHeaders() - 返回全部響應頭信息(響應頭名和值),若是響應頭尚未接收,則返回 null。注意:使用該方法獲取的 response headers 與在開發者工具 Network 面板中看到的響應頭不一致
getResponseHeader() - 返回指定響應頭的值,若是響應頭尚未被接收,或該響應頭不存在,則返回 null。注意:使用該方法獲取某些響應頭時,瀏覽器會拋出異常,具體緣由以下:
W3C的 xhr 標準中作了限制,規定客戶端沒法獲取 response 中的 Set-Cookie
、Set-Cookie2
這2個字段,不管是同域仍是跨域請求。
W3C 的 cors 標準對於跨域請求也作了限制,規定對於跨域請求,客戶端容許獲取的response header字段只限於 simple response header (常見的 simple response header 以下)
和 Access-Control-Expose-Headers。
open() - 初始化一個請求:
方法簽名:
void open(
DOMString method,
DOMString url,
optional boolean async,
optional DOMString user,
optional DOMString password
);複製代碼
參數:
備註:
SyntaxError
異常CONNECT
、TRACE
或 TRACK
將會拋出 SecurityError
異常overrideMimeType() - 重寫由服務器返回的 MIME 類型。例如,能夠用於強制把響應流當作 text/xml
來解析,即便服務器沒有指明數據是這個類型。注意:這個方法必須在 send() 以前被調用。
send() - 發送請求。若是該請求是異步模式(默認),該方法會馬上返回。相反,若是請求是同步模式,則直到請求的響應徹底接受之後,該方法纔會返回。注意:全部相關的事件綁定必須在調用 send() 方法以前進行。
方法簽名:
void send();
void send(ArrayBuffer data);
void send(Blob data);
void send(Document data);
void send(DOMString? data);
void send(FormData data);複製代碼
setRequestHeader() - 設置 HTTP 請求頭信息。注意:在這以前,你必須確認已經調用了 open() 方法打開了一個 url
方法簽名:
void setRequestHeader(
DOMString header,
DOMString value
);複製代碼
參數:
sendAsBinary() - 發送二進制的 send() 方法的變種。
方法簽名:
void sendAsBinary(
in DOMString body
);複製代碼
參數:
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 | 未實現 | 未實現 | 未實現 |
responseType = 'json' | 未實現 | 10 | 未實現 | 12 | 未實現 |
Progress Events | 7 | 3.5 | 10 | 12 | ? |
withCredentials | 3 | 3.5 | 10 | 12 | 4 |
首先,建立一個 XMLHttpRequest 對象:
var xhr = new XMLHttpRequest();複製代碼
而後,向服務器發出一個 HTTP 請求:
xhr.open('GET', 'example.php');
xhr.send();複製代碼
接着,就等待遠程主機作出迴應。這時須要監控XMLHttpRequest對象的狀態變化,指定回調函數。
xhr.onreadystatechange = function(){
if ( xhr.readyState == 4 && xhr.status == 200 ) {
alert( xhr.responseText );
} else {
alert( xhr.statusText );
}
};複製代碼
上面的代碼包含了老版本 XMLHttpRequest 對象的主要屬性:
XMLHttpRequest Level 2 針對 XMLHttpRequest Level 1 的缺點,作了大幅改進。具體以下:
新版本 XMLHttpRequest 對象,增長了 timeout 屬性,能夠設置HTTP請求的時限。
xhr.timeout = 3000;複製代碼
上面的語句,將最長等待時間設爲3000毫秒。過了這個時限,就自動中止HTTP請求。與之配套的還有一個timeout事件,用來指定回調函數。
xhr.ontimeout = function(event){
console.log('請求超時');
}複製代碼
AJAX 操做每每用來傳遞表單數據。爲了方便表單處理,HTML 5新增了一個 FormData 對象,能夠用於模擬表單。
構造函數 FormData()
用於建立一個新的 FormData 對象。
語法
var formData = new FormData(form)複製代碼
<form>
表單元素。當使用 form 參數,建立的 FormData 對象會自動將 form 中的表單值也包含進去,文件內容會被編碼首先,新建一個 FormData 對象:
var formData = new FormData();複製代碼
而後,爲它添加表單項:
formData.append('username', 'semlinker');
formData.append('id', 2005821040);複製代碼
最後,直接傳送這個FormData對象。這與提交網頁表單的效果,徹底同樣。
xhr.send(formData);複製代碼
FormData 對象也能夠用來獲取網頁表單的值。
var form = document.getElementById('myform'); // 獲取頁面上表單對象
var formData = new FormData(form);
formData.append('username', 'semlinker'); // 添加一個表單項
xhr.open('POST', form.action);
xhr.send(formData);複製代碼
新版 XMLHttpRequest 對象,不只能夠發送文本信息,還能夠上傳文件。
1.爲了上傳文件, 咱們得先選中一個文件. 一個 type 爲 file 的 input 輸入框
<input id="input" type="file">複製代碼
2.而後用 FormData 對象包裹選中的文件
var input = document.getElementById("input"),
formData = new FormData();
formData.append("file",input.files[0]); // file名稱與後臺接收的名稱一致複製代碼
3.設置上傳地址和請求方法
var url = "http://localhost:3000/upload",
method = "POST";複製代碼
4.發送 FormData 對象
xhr.send(formData);複製代碼
新版本的 XMLHttpRequest 對象,能夠向不一樣域名的服務器發出 HTTP 請求。這叫作 "跨域資源共享"(Cross-origin resource sharing,簡稱 CORS)。
使用"跨域資源共享"的前提,是瀏覽器必須支持這個功能,並且服務器端必須贊成這種"跨域"。若是可以知足上面的條件,則代碼的寫法與不跨域的請求徹底同樣。
xhr.open('GET', 'http://other.server/and/path/to/script');複製代碼
XMLHttpRequest Level 1 XMLHttpRequest 對象只能處理文本數據,新版則能夠處理二進制數據。從服務器取回二進制數據,較新的方法是使用新增的 responseType 屬性。若是服務器返回文本數據,這個屬性的值是 "TEXT",這是默認值。較新的瀏覽器還支持其餘值,也就是說,能夠接收其餘格式的數據。
你能夠把 responseType 設爲 blob,表示服務器傳回的是二進制對象。
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png');
xhr.responseType = 'blob';
xhr.send();複製代碼
接收數據的時候,用瀏覽器自帶的 Blob 對象便可。
一個 Blob 對象表示一個不可變的, 原始數據的相似文件對象。Blob 表示的數據不必定是一個 JavaScript 原生格式。
File
接口基於Blob,繼承 blob功能並將其擴展爲支持用戶系統上的文件。
var blob = new Blob([xhr.response], {type: 'image/png'});複製代碼
更多示例請參考 發送和接收二進制數據 。
新版本的 XMLHttpRequest 對象,傳送數據的時候,有一個 progress 事件,用來返回進度信息。
它分紅上傳和下載兩種狀況。下載的 progress 事件屬於 XMLHttpRequest 對象,上傳的 progress 事件屬於XMLHttpRequest.upload 對象。
咱們先定義progress事件的回調函數:
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;複製代碼
而後,在回調函數裏面,使用這個事件的一些屬性。
function updateProgress(event) {
if (event.lengthComputable) {
var percentComplete = event.loaded / event.total;
}
}複製代碼
上面的代碼中,event.total 是須要傳輸的總字節,event.loaded 是已經傳輸的字節。若是event.lengthComputable 不爲真,則 event.total 等於0。
各個瀏覽器 XMLHttpRequest Level 2 的兼容性 - Can I use/xhr2
XHR 能夠傳輸基於文本和二進制數據。實際上,瀏覽器能夠爲各類本地數據類型提供自動編碼和解碼,這樣可讓應用程序將這些類型直接傳遞給XHR,以便正確編碼,反之亦然,這些類型能夠由瀏覽器自動解碼:
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) 設置響應的數據類型爲 blob
(2) 基於Blob建立一個惟一的對象URL,並做爲圖片的源地址 (URL.createObjectURL())
(3) 圖片加載成功後釋放對象的URL(URL.revokeObjectURL())
經過 XHR 上傳數據對於全部數據類型來講都是簡單而有效的。實際上,惟一的區別是當咱們在XHR請求中調用 send() 時,咱們需傳遞不一樣的數據對象。其他的由瀏覽器處理:
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) 發送普通的文本到服務器
(2) 經過 FormData API 建立動態表單
(3) 發送 FormData 數據到服務器
(4) 建立 Unit8Array 數組 (Uint8Array 數組類型表示一個8位無符號整型數組,建立時內容被初始化爲0)
(5) 發送二進制數據到服務器
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) 一個任意的數據塊 (二進制或文本)
(2) 將數據庫大小設置爲 1MB
(3) 迭代提供的數據,增量爲1MB
(4) 設置上傳的數據範圍 (Content-Range請求頭)
(5) 經過 XHR 上傳 1MB 數據塊
XHR 對象提供了一系列 API,用於監聽進度事件,表示請求的當前狀態:
事件類型 | 描述 | 觸發次數 |
---|---|---|
loadstart | 開始傳輸 | 1次 |
progress | 傳輸中 | 0次或屢次 |
error | 傳輸中出現錯誤 | 0次或1次 |
abort | 傳輸被用戶取消 | 0次或1次 |
load | 傳輸成功 | 0次或1次 |
loadend | 傳輸完成 | 1次 |
每一個 XHR 傳輸都以 loadstart
事件開始,並以 loadend
事件結束,並在這兩個事件期間觸發一個或多個附加事件來指示傳輸的狀態。所以,爲了監控進度,應用程序能夠在 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) 設置請求超時時間爲 5,000 ms (默認無超時時間)
(2) 註冊成功回調
(3) 註冊異常回調
(4) 計算已完成的進度
(5) 註冊上傳進度事件回調
(6) 註冊下載進度事件回調
在某些狀況下,應用程序可能須要或但願逐步處理數據流:將數據上傳到服務器,使其在客戶機上可用,或者在從服務器下載數據時,進行流式處理。
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) 監聽 onreadystatechange 事件
(2) 從部分響應中提取新數據
(3) 更新處理的字節偏移
這個例子能夠在大多數現代瀏覽器中使用。可是,性能並很差,並且還有大量的注意事項和問題:
從服務器檢索更新的最簡單的策略之一是讓客戶端進行按期檢查:客戶端能夠以週期性間隔(輪詢服務器)啓動後臺XHR請求,以檢查更新。若是新數據在服務器上可用,則在響應中返回,不然響應爲空。
定時輪詢的方式很簡單,但若是定時間隔很短的話,也是很低效。所以設置合適的時間間隔顯得相當重要:輪詢間隔時間過長,會致使更新不及時,然而間隔時間太短的話,則會致使客戶端與服務器沒必要要的流程和高開銷。接下來咱們來看一個簡單的示例:
function checkUpdates(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() { ... }; // 1
xhr.send();
}
setInterval(function() { checkUpdates('/updates') }, 60000); // 2複製代碼
(1) 處理服務端接收的數據
(2) 設置定時輪詢時間爲 60s
定時輪詢會產生如下的問題:
每一個瀏覽器發起 HTTP 請求時都將攜帶額外的 500 - 800 字節的元數據 (請求頭),如 user-agent、accept、Cache-Control 緩存控制頭等。更糟糕的是,500 - 800 字節是理想的狀況,若是攜帶 Cookies 信息,那麼這個數值將會更大。總而言之,這些未壓縮的 HTTP 元數據會引發很大開銷。
平均每一個 HTTP 1.x 請求會增長 大約 800字節的請求和響應開銷 (詳細信息能夠查看 - Measuring and Controlling Protocol Overhead) 。另外在客戶端登陸後,咱們還將產生一個額外的身份驗證 cookie 和 消息ID; 假設這又增長了50個字節。所以,不返回新消息的請求將產生 850字節開銷!如今假設咱們有10,000個客戶端,全部的輪詢間隔時間都是60秒:
$$
(850 bytes 8 bits 10,000) / 60 seconds ≈ 1.13 Mbps
$$
每一個客戶端在每一個請求上發送 850 字節的數據,這轉換爲每秒 167 個請求,服務器上的吞吐量大約爲 1.13 Mbps!這不是一個固定的值,此外該計算值仍是在假設服務器沒有向任何客戶端傳遞任何新的消息的理想狀況下計算而得的。
週期性輪詢的挑戰在於有可能進行許多沒必要要的和空的檢查。考慮到這一點,若是咱們對輪詢工做流程進行了輕微的修改,而不是在沒有更新可用的狀況下返回一個空的響應,咱們能夠保持鏈接空閒,直到更新可用嗎?
(圖片來源 - hpbn.co/xmlhttprequ…
經過保持長鏈接,直到更新可用,數據能夠當即發送到客戶端,一旦它在服務器上可用。所以,長時間輪詢爲消息延遲提供了最佳的狀況,而且還消除了空檢查,這減小了 XHR 請求的數量和輪詢的整體開銷。一旦更新被傳遞,長的輪詢請求完成,而且客戶端能夠發出另外一個長輪詢請求並等待下一個可用的消息:
function checkUpdates(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() { // 1
...
checkUpdates('/updates'); // 2
};
xhr.send();
}
checkUpdates('/updates'); // 3複製代碼
(1) 處理接收到的數據並啓動下一輪檢測更新
(2) 啓動下一輪檢測更新
(3) 發起首次更新請求
那麼長時間輪詢老是比按期輪詢更好的選擇?除非消息到達率已知且不變,不然長輪詢將始終提供更短的消息延遲。
另外一方面,開銷討論須要更細微的觀點。首先,請注意,每一個傳遞的消息仍然引發相同的 HTTP 開銷;每一個新消息都是獨立的 HTTP 請求。可是,若是消息到達率高,那麼長時間輪詢會比按期輪詢發出更多的XHR請求!
長輪詢經過最小化消息延遲來動態地適應消息到達速率,這是您可能想要的或可能不須要的行爲。若是對消息延遲要求不高的話,則定時輪詢多是更有效的傳輸方式 - 例如,若是消息更新速率較高,則定時輪詢提供簡單的 "消息聚合" 機制 (即合併必定時間內的消息),這能夠減小請求數量並提升移動設備的電池壽命。
Mock.js 是一款模擬數據生成器,旨在幫助前端攻城師獨立於後端進行開發,幫助編寫單元測試。提供瞭如下模擬功能:
詳細信息,請查看 - Mock.js 文檔
Zone 是下一個 ECMAScript 規範的建議之一。Angular 團隊實現了 JavaScript 版本的 zone.js ,它是用於攔截和跟蹤異步工做的機制。
Zone 是一個全局的對象,用來配置有關如何攔截和跟蹤異步回調的規則。Zone 有如下能力:
zone.js 內部使用 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 經過將 HTTP 請求-應答模型封裝在一個漸進流式接口中,幫助網頁應用快速應答。它將 streaming 和downloading 間的轉換與SAX和DOM間JSON的解析整合在一塊兒。它是個很是小的庫,不依賴於其餘程序庫。它能夠在 ajax 請求結束前就開始解析 json 變得十分容易,從而提升應用的應答速度。另外,它支持 Node.js 框架,還能夠讀入除了 http 外的其餘流。
有興趣的讀者,推薦看一下官網的可交互的演示示例 - Why Oboe.js
(備註:該庫就是文中 - 使用XHR流式傳輸數據章節的實際應用,不信往下看)
// oboe-browser.js 源碼片斷
function handleProgress() {
var textSoFar = xhr.responseText,
newText = textSoFar.substr(numberOfCharsAlreadyGivenToCallback);
if( newText ) {
emitStreamData( newText );
}
numberOfCharsAlreadyGivenToCallback = len(textSoFar);
}複製代碼
fetch 函數是一個基於 Promise 的機制,用於在瀏覽器中以編程方式發送 Web 請求。該項目是實現標準 Fetch 規範的一個子集的 polyfill ,足以做爲傳統 Web 應用程序中 XMLHttpRequest 的代替品。
詳細信息,請參考 - Github - fetch
Fetch API 兼容性,請參考 - Can I use Fetch
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}複製代碼
代碼片斷來源 - ArrayBuffer與字符串的互相轉換
function str2ab(str) {
var buf = new ArrayBuffer(str.length * 2); // 每一個字符佔用2個字節
var bufView = new Uint16Array(buf);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}複製代碼
代碼片斷來源 - ArrayBuffer與字符串的互相轉換
// 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) {}
}複製代碼
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);
};
}複製代碼
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;
}複製代碼
應用示例:
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);複製代碼
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
function paramParser(rawParams: string = ''): Map<string, string[]> {
const map = new Map<string, string[]>();
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 轉換爲 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;
}複製代碼
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();複製代碼
var xhr = new XMLHttpRequest();
xhr.open('POST','/upload');
xhr.onload = function() { ... };
xhr.send("text string");複製代碼
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);複製代碼
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.onload = function() { ... };
var uInt8Array = new Uint8Array([1, 2, 3]);
xhr.send(uInt8Array.buffer);複製代碼
progress 元素
<progress id="uploadprogress" min="0" max="100" value="0">0</progress>複製代碼
定義 progress 事件的回調函數
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,由於這裏須要區分下載和上傳,下載也有一個progress事件。
XMLHttpRequest 返回 status 時,會執行如下步驟:
另外當訪問本地文件資源或在 Android 4.1 stock browser 中從應用緩存中獲取文件時,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 對象複製代碼
client . send([body = null])
Initiates the request. The optional argument provides the request body. The argument is ignored if request method is
GET
orHEAD
. —— xhr.spec
經過 XMLHttpRequest 規範,咱們知道當請求方法是 GET 或 HEAD 時,send()
方法的 body 參數值將會被忽略。那麼對於咱們經常使用的 GET 請求,咱們要怎麼傳遞參數呢?解決參數傳遞可使用如下兩種方式:
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時,設置請求體爲空複製代碼
在平常開發中,咱們最經常使用的方式是傳遞參數對象,所以咱們能夠封裝一個 formatParams()
來實現參數格式,具體示例以下:
formatParams() 函數:
function formatParams( params ){
return "?" + Object
.keys(params)
.map(function(key){
return key+"="+params[key]
})
.join("&")
}複製代碼
應用示例:
var endpoint = "https://api.example.com/endpoint";
var params = {
a: 1,
b: 2,
c: 3
};
var url = endpoint + formatParams(params); // 實際應用中須要判斷endpoint是否已經包含查詢參數
// => "https://api.example.com/endpoint?a=1&b=2&c=3";複製代碼
一些經常使用的 AJAX 庫,如 jQuery、zepto 等,內部已經封裝了參數序列化的方法 (如:jquery.param),咱們直接調用頂層的 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
send() 方法簽名:
void send();
void send(ArrayBuffer data);
void send(Blob data);
void send(Document data);
void send(DOMString? data);
void send(FormData data);複製代碼
POST請求示例
發送 POST 請求一般須要如下步驟:
Content-Type
請求頭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);複製代碼
簡單請求
一些不會觸發 CORS preflight 的請求被稱爲 "簡單請求",雖然 Fetch (定義 CORS的) 不使用這個術語。知足下述條件的就是 "簡單請求":
Content-Type
值有:
預請求
不一樣於上面討論的簡單請求,"預請求" 要求必須先發送一個 OPTIONS
方法請求給目的站點,來查明這個跨站請求對於目的站點是否是安全的可接受的。這樣作,是由於跨站請求可能會對目的站點的數據產生影響。 當請求具有如下條件,就會被當成預請求處理:
詳細的信息,請參考 - MDN - HTTP 訪問控制 (CORS)
在如下狀況下,XMLHttpRequest 對象不會被垃圾回收:
OPENED
且已設置 send()
的標識符若是 XMLHttpRequest 對象在鏈接尚存打開時被垃圾回收機制回收了,用戶代理必須終止請求。
詳細的信息,請參考 - 99%的人都理解錯了HTTP中GET與POST的區別
詳細的信息,請參考 - 知乎 - 怎樣防止重複發送 Ajax 請求
衆所周知,大部分的搜索引擎爬蟲都不會執行 JS,也就是說,若是頁面內容由 Ajax 返回的話,搜索引擎是爬取不到部份內容的,也就無從作 SEO (搜索引擎優化)了。國外的 prerender.io 網站提供了一套比較成熟的方案,可是須要付費的。接下來咱們來看一下,怎麼 PhantomJS 爲咱們的站點作 SEO。
詳細的信息,請參考 - 用PhantomJS來給AJAX站點作SEO優化