Ajax,在它最基本的層面,是一種與服務器通信而不重載當前頁面的方法,數據可從服務器得到或發送給服務器。有多種不一樣的方法構造這種通信通道,每種方法都有本身的優點和限制。
有五種經常使用技術用於向服務器請求數據:
(1)XMLHttpRequest (XHR)
(2)動態腳本標籤插入
(3)框架
(4)Comet
(5)多部分的XHR
在現代高性能JavaScript中使用的三種技術是XHR,動態腳本標籤插入和多部分的XHR。使用Comet和iframe(做爲數據傳輸技術)每每是極限狀況,不在這裏討論。javascript
1、XMLHttpRequest
目前最經常使用的方法中,XMLHttpRequest(XHR)用來異步收發數據。全部現代瀏覽器都可以很好地支持它,並且可以精細地控制發送請求和數據接收。你能夠向請求報文中添加任意的頭信息和參數(包括GET和POST),並讀取從服務器返回的頭信息,以及響應文本自身。如下是使用示例:
var url = '/data.php';
var params = [
'id=934875',
'limit=20'
];
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState=== 4) {
var responseHeaders = req.getAllResponseHeaders();
var data = req.responseText;
}
}
req.open('GET', url + '?' + params.join('&'), true);
req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
req.send(null);
此例顯示瞭如何從URL請求數據,使用參數,以及如何讀取響應報文和頭信息。readyState等於4表示整個響應報文已經收並完可用於操做。
readyState等於3則表示此時正在與服務器交互,響應報文還在傳輸之中。這就是所謂的「流」,它是提升數據請求性能的強大工具:
req.onreadystatechange = function() {
if (req.readyState=== 3) {
var dataSoFar = req.responseText;
…
}
else if (req.readyState=== 4) {
var data = req.responseText;
…
}
}
因爲XHR提供了高級別的控制,瀏覽器在上面增長了一些限制。你不能使用XHR從當前運行的代碼域以外請求數據,並且老版本的IE 也不提供readyState3,它不支持流。從請求返回的數據像一個字符串或者一個XML對象那樣對待,這意味着處理大量數據將至關緩慢。
儘管有這些缺點,XHR仍舊是最經常使用的請求數據技術,也是最強大的,它應當成爲你的首選。
當使用XHR請求數據時,你能夠選擇POST 或GET。若是請求不改變服務器狀態只是取回數據(又稱做冪等動做)則使用GET。GET請求被緩衝起來,若是你屢次提取相同的數據可提升性能。
只有當URL和參數的長度超過了2'048個字符時才使用POST提取數據。由於Internet Explorer限制URL的長度,過長將致使請求(參數)被截斷。
2、動態腳本標籤插入
該技術克服了XHR的最大限制:它能夠從不一樣域的服務器上獲取數據。這是一種黑客技術,而不是實例化一個專用對象,你用JavaScript建立了一個新腳本標籤,並將它的源屬性設置爲一個指向不一樣域的URL。
var scriptElement = document.createElement('script');
scriptElement.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName_r('head')[0].appendChild(scriptElement);
可是動態腳本標籤插入與XHR相比只提供更少的控制。你不能經過請求發送信息頭。參數只能經過GET方法傳遞,不能用POST。你不能設置請求的超時或重試,實際上,你不須要知道它是否失敗了。你必須等待全部數據返回以後才能夠訪問它們。你不能訪問響應信息頭或者像訪問字符串那樣訪問整個響應報文。
最後一點很是重要。由於響應報文被用做腳本標籤的源碼,它必須是可執行的JavaScript。你不能使用裸XML,或者裸JSON,任何數據,不管什麼格式,必須在一個回調函數之中被組裝起來。
var scriptElement = document.createElement('script');
scriptElement.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName_r('head')[0].appendChild(scriptElement);
function jsonCallback(jsonString) {
var data = ('(' + jsonString + ')');
}
在這個例子中,lib.js 文件將調用jsonCallback 函數組裝數據:
jsonCallback({ "status": 1, "colors": [ "#fff", "#000", "#ff0000" ] });
儘管有這些限制,此技術仍然很是迅速。其響應結果是運行JavaScript,而不是做爲字符串必須被進一步處理。正由於如此,它多是客戶端上獲取並解析數據最快的方法。咱們比較了動態腳本標籤插入和XHR的性能,在本章後面JSON 一節中。
請當心使用這種技術從你不能直接控制的服務器上請求數據。JavaScript沒有權限或訪問控制的概念,因此你的頁面上任何使用動態腳本標籤插入的代碼均可以徹底控制整個頁面。包括修改任何內容、將用戶重定向到另外一個站點,或跟蹤他們在頁面上的操做並將數據發送給第三方。使用外部來源的代碼時務必很是當心。
3、多部分XHR
多部分XHR(MXHR)容許你只用一個HTTP 請求就能夠從服務器端獲取多個資源。它經過將資源(能夠是CSS 文件,HTML 片斷,JavaScript代碼,或base64 編碼的圖片)打包成一個由特定分隔符界定的大字符串,從服務器端發送到客戶端。JavaScript代碼處理此長字符串,根據它的媒體類型和其餘「信息頭」解析出每一個資源。
讓咱們從頭至尾跟隨這個過程。首先,發送一個請求向服務器索取幾個圖像資源:
var req = new XMLHttpRequest();
req.open('GET', 'rollup_images.php', true);
req.onreadystatechange = function() {
if (req.readyState== 4) {
splitImages(req.responseText);
}
};
req.send(null);
這是一個很是簡單的請求。你向rollup_images.php 要求數據,一旦你收到返回結果,就將它交給函數splitImages處理。
下一步,服務器讀取圖片並將它們轉換爲字符串:
images=array(′kitten.jpg′,′sunset.jpg′,′baby.jpg′);foreach(images=array(′kitten.jpg′,′sunset.jpg′,′baby.jpg′);foreach(images as image)$imagefh=fopen($image,′r′);$imagedata=fread($imagefh,filesize($image));fclose($imagefh);$payloads[]=base64encode($imagedata);image)$imagefh=fopen($image,′r′);$imagedata=fread($imagefh,filesize($image));fclose($imagefh);$payloads[]=base64encode($imagedata);newline = chr(1);
echo implode(newline,newline,payloads);
這段PHP代碼讀取三個圖片,並將它們轉換成base64字符串。它們之間用一個簡單的字符,UNICODE的1,鏈接起來,而後返回給客戶端。
而後回到客戶端,此數據由splitImage 函數處理:
function splitImages(imageString) {
var imageData = imageString.split("\u0001");
var imageElement;
for (var i = 0, len = imageData.length; i < len; i++) {
imageElement = document.createElement('img');
imageElement.src = 'data:image/jpeg;base64,' + imageData[i];
document.getElementById('container').appendChild(imageElement);
}
}
此函數將拼接而成的字符串分解爲三段。每段用於建立一個圖像元素,而後將圖像元素插入頁面中。圖像不是從base64 轉換成二進制,而是使用data:URL 並指定image/jpeg 媒體類型。
最終結果是:在一次HTTP 請求中向瀏覽器傳入了三張圖片。也能夠傳入20 張或100 張,響應報文會更大,但也只是一次HTTP 請求。它也能夠擴展至其餘類型的資源。JavaScript文件,CSS 文件,HTML片斷,許多類型的圖片均可以合併成一次響應。任何數據類型均可做爲一個JavaScript處理的字符串被髮送。下面的函數用於將JavaScript代碼、CSS 樣式表和圖片轉換爲瀏覽器可用的資源:
function handleImageData(data, mimeType) {
var img = document.createElement('img');
img.src = 'data:' + mimeType + ';base64,' + data;
return img;
}
function handleCss(data) {
var style = document.createElement('style');
style.type = 'text/css';
var node = document.createTextNode(data);
style.appendChild(node);
document.getElementsByTagName_r('head')[0].appendChild(style);
}
function handleJavaScript(data) {
(data);
}
因爲MXHR響應報文愈來愈大,有必要在每一個資源收到時馬上處理,而不是等待整個響應報文接收完成。這能夠經過監聽readyState3 實現:
var req = new XMLHttpRequest();
var getLatestPacketInterval, lastLength = 0;
req.open('GET', 'rollup_images.php', true);
req.onreadystatechange = readyStateHandler;
req.send(null);
function readyStateHandler{
if (req.readyState=== 3 && getLatestPacketInterval === null) {
getLatestPacketInterval = window.setInterval(function() {
getLatestPacket();
}, 15);
}
if (req.readyState=== 4) {
clearInterval(getLatestPacketInterval);
getLatestPacket();
}
}
function getLatestPacket() {
var length = req.responseText.length;
var packet = req.responseText.substring(lastLength, length);
processPacket(packet);
lastLength = length;
}
當readyState3第一次發出時,啓動了一個定時器。每隔15毫秒檢查一次響應報文中的新數據。數據片斷被收集起來直到發現一個分隔符,而後一切都做爲一個完整的資源處理。以健壯的方式使用MXHR的代碼很複雜但值得進一步研究。
使用此技術有一些缺點,其中最大的缺點是以此方法得到的資源不能被瀏覽器緩存。若是你使用MXHR獲取一個特定的CSS 文件而後在下一個頁面中正常加載它,它不在緩存中。由於整批資源是做爲一個長字符串傳輸的,而後由JavaScript代碼分割。因爲沒有辦法用程序將文件放入瀏覽器緩存中,因此用這種方法獲取的資源也沒法存放在那裏。
另外一個缺點是:老版本的Internet Explorer不支持readyState3或data: URL。Internet Explorer 8兩個都支持,但在Internet Explorer 6和7中必須設法變通。
儘管有這些缺點,但某些狀況下MXHR仍然顯著提升了總體頁面的性能:網頁包含許多其餘地方不會用到的資源(因此不須要緩存),尤爲是圖片。
網站爲每一個頁面使用了獨一無二的打包的JavaScript或CSS文件以減小HTTP請求,由於它們對每一個頁面來講是獨一的,因此不須要從緩存中讀取,除非從新載入特定頁面。
因爲HTTP請求是Ajax中最極端的瓶頸之一,減小其需求數量對整個頁面性能有很大影響。尤爲是當你將100個圖片請求轉化爲一個MXHR請求時。Ad hoc 在現代瀏覽器上測試了大量圖片,其結果顯示出此技術比逐個請求快了4到10倍。
有時你不關心接收數據,而只要將數據發送給服務器。你能夠發送用戶的非私有信息以備往後分析,或者捕獲全部腳本錯誤而後將有關細節發送給服務器進行記錄和提示。當數據只需發送給服務器時,有兩種普遍應用的技術:XHR和燈標。
(1) XMLHttpRequest
雖然XHR主要用於從服務器獲取數據,它也能夠用來將數據發回。數據能夠用GET或POST 方式發回,以及任意數量的HTTP 信息頭。這給你很大靈活性。當你向服務器發回的數據量超過瀏覽器的最大URL長度時XHR特別有用。這種狀況下,你能夠用POST 方式發回數據:
var url = '/data.php';
var params = [
'id=934875',
'limit=20'
];
var req = new XMLHttpRequest();
req.onerror = function() {
// Error.
};
req.onreadystatechange = function() {
if (req.readyState== 4) {
// Success.
}
};
req.open('POST', url, true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setRequestHeader('Content-Length', params.length);
req.send(params.join('&'));
正如你在這個例子中看到的,若是失敗了咱們什麼也不作。當咱們用XHR捕獲登錄用戶統計信息時這麼作一般沒什麼問題,可是,若是發送到服務器的是相當重要的數據,你能夠添加代碼在失敗時重試:
function xhrPost(url, params, callback) {
var req = new XMLHttpRequest();
req.onerror = function() {
setTimeout(function() {
xhrPost(url, params, callback);
}, 1000);
};
req.onreadystatechange = function() {
if (req.readyState== 4) {
if (callback && typeof callback === 'function') {
callback();
}
}
};
req.open('POST', url, true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setRequestHeader('Content-Length', params.length);
req.send(params.join('&'));
}
當使用XHR將數據發回服務器時,它比使用GET要快。這是由於對少許數據而言,向服務器發送一個GET請求要佔用一個單獨的數據包。另外一方面,一個POST至少發送兩個數據包,一個用於信息頭。另外一個用於POST體。POST更適合於向服務器發送大量數據,即由於它不關心額外數據包的數量,又由於Internet Explorer 的URL長度限制,它不可能使用過長的GET請求。
(2) 燈標
此技術與動態腳本標籤插入很是相似。JavaScript用於建立一個新的Image 對象,將src 設置爲服務器上一個腳本文件的URL。此URL 包含咱們打算經過GET格式傳回的鍵值對數據。注意並無建立img 元素或者將它們插入到DOM 中。
var url = '/status_tracker.php';
var params = [
'step=2',
'time=1248027314'
];
(new Image()).src = url + '?' + params.join('&');
服務器取得此數據並保存下來,而沒必要向客戶端返回什麼,所以沒有實際的圖像顯示。這是將信息發回服務器的最有效方法。其開銷很小,並且任何服務器端錯誤都不會影響客戶端。
簡單的圖像燈標意味着你所能作的受到限制。你不能發送POST 數據,因此你被URL 長度限制在一個至關小的字符數量上。你能夠用很是有限的方法接收返回數據。能夠監聽Image 對象的load 事件,它能夠告訴你服務器端是否成功接收了數據。你還能夠檢查服務器返回圖片的寬度和高度(若是返回了一張圖片)並用這些數字通知你服務器的狀態。例如,寬度爲1 表示「成功」,2 表示「重試」。
若是你不須要爲此響應返回數據,那麼你應當發送一個204 No Content 響應代碼,無消息正文。它將阻止客戶端繼續等待永遠不會到來的消息體:
var url = '/status_tracker.php';
var params = [
'step=2',
'time=1248027314'
];
var beacon = new Image();
beacon.src = url + '?' + params.join('&');
beacon.onload = function() {
if (this.width == 1) {
// Success.
}
else if (this.width == 2) {
// Failure; create another beacon and try again.
}
};
beacon.onerror = function() {
// Error; wait a bit, then create another beacon and try again.
};
燈標是向服務器回送數據最快和最有效的方法。服務器根本不須要發回任何響應正文,因此你沒必要擔憂客戶端下載數據。惟一的缺點是接收到的響應類型是受限的。若是你須要向客戶端返回大量數據,那麼使用XHR。若是你只關心將數據發送到服務器端(可能須要極少的回覆),那麼使用圖像燈標。php