讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議141:比較常用的服務器請求方法 >

建議141:比較常用的服務器請求方法

目前,主要有5種常用技術用於向服務器請求數據:

❑XMLHttpRequest(XHR)

❑Dynamic script tag insertion(動態腳本標籤插入)

❑iframe

❑Comet

❑Multipart XHR(多部分XHR)

在現代高性能JavaScript中,推薦使用的技術是XHR、動態腳本標籤插入和多部分XHR。作為數據傳輸技術,往往在極限情況下使用Comet和iframe。

(1)XHR

目前最常用的方法是使用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也不提供readyState=3,不支持流。像對待一個字符串或者一個XML對像那樣對待從請求返回的數據,這意味著處理大量數據將相當緩慢。

儘管有這些缺點,XHR仍然是最常用的請求數據技術,也是最強大的,因此它應當成為異步數據通信的首選。當使用XHR請求數據時,可以選擇POST或GET。如果請求不改變服務器狀態只是取回數據,則使用GET。GET請求被緩衝起來,多次提取相同的數據可提高性能。

只有當URL和參數的長度超過了2048個字符時才使用POST提取數據,這是因為IE限制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。任何數據,無論什麼格式,必須在一個回調函數之中被組裝起來。


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,而不是必須作為字符串被進一步處理。正因為如此,它是客戶端獲取並解析數據最快的方法。

由於JavaScript中沒有權限或訪問控制的概念,所以頁面上任何使用動態腳本標籤插入的代碼都可以完全控制整個頁面,包括修改任何內容、將用戶重定向到另一個站點,以及跟蹤在頁面上的操作並將數據發送給第三方。使用外部來源的代碼時要非常小心。

(3)多部分XHR

多部分XHR允許只用一個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 as$image){

$image_fh=fopen($image,\'r\');

$image_data=fread($image_fh,filesize($image));

fclose($image_fh);

$payloads=base64_encode($image_data);

}

$newline=chr(1);

echo implode($newline,$payloads);


這段PHP代碼讀取3張圖片,並將它們轉換成base64字符串。這些字符串之間用一個簡單的字符連接起來,然後返回給客戶端。

接下來回到客戶端,此數據由splitImages函數處理:


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);

}

}


此函數將拼接而成的字符串分解為3段,每段用於創建一個圖像元素,然後將圖像元素插入頁面中。圖像不是從base64轉換成二進制,而是使用data:URL並指定image/jpeg媒體類型。

最終結果:在一次HTTP請求中向瀏覽器傳入了3張圖片。也可以傳入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響應報文越來越大,有必要在每個資源收到響應時立刻處理,而不是等待整個響應報文接收完成後再處理。這可以通過監聽readyState=3實現。


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;

}


當readyState=3第一次發出時,啟動一個定時器,每隔15 ms檢查一次響應報文中的新數據,數據片段被收集起來直到發現一個分隔符,然後一切都作為一個完整的資源處理。

以健壯的方式使用MXHR的代碼很複雜,但值得進一步研究。完整的庫可參見http://techfoolery.com/mxhr/。

使用這種技術有一些缺點,其中最大的缺點是以此方法獲得的資源不能被瀏覽器緩存。使用MXHR獲取一個特定的CSS文件,然後在下一個頁面中正常加載它,會發現它不在緩存中,這是因為整批資源作為一個長字符串傳輸,然後由JavaScript代碼分割。由於沒有辦法通過程序將文件放入瀏覽器緩存中,因此用這種方法獲取的資源也無法存放在瀏覽器緩存中。另外,早期版本的IE不支持readyState=3或data:URL,從IE 8開始支持這些技術,在IE 6和IE 7中必須設法變通。在某些情況下,MXHR能夠顯著地提高整體頁面的性能:

❑網頁包含許多其他地方不會用到的資源,不需要緩存資源,尤其是圖片。

❑網站為每個頁面使用了獨一無二的打包的JavaScript或CSS文件以減少HTTP請求,因為它們對每個頁面來說是獨特的,所以不需要從緩存中讀取,除非重新載入特定頁面。

❑由於HTTP請求是Ajax中最極端的瓶頸之一,減少其需求數量對整個頁面性能有很大影響,尤其是將100個圖片請求轉化為一個MXHR請求時能夠極大地提高響應速度。