《高性能JavaScript》讀書筆記⑦:Ajax

  • 為了避免頁面資源一窩蜂地下載,Ajax提供異步的方式在客戶端和服務器端之間傳輸數(shù)據(jù)。
  • Ajax是高性能JavaScript的基礎,它甚至可以只用一個HTTP請求就獲得整個頁面的資源。只要我們選擇合適和傳輸方式和最有效的數(shù)據(jù)格式,可以顯著改善用戶的交互體驗。

數(shù)據(jù)傳輸(Data Transmission)

  • 從最基本的層面來說,Ajax實際上是一種與服務器通信而無須重載頁面的方法。
  • 同時,也有多種方法建立這種通信通道,而不同的方法有各自的優(yōu)點和限制。
數(shù)據(jù)請求(Requesting Data)
  • 頁面向服務器請求數(shù)據(jù),常用的有五種:

    • XMLHttpRequest(XHR)
    • Dynamic script tag insertion 動態(tài)腳本注入
    • iframes
    • Comet
    • Multipart XHR

    (加粗字體的是現(xiàn)代JavaScript常用的技術,iframesComet是極端情況下使用,這里不做討論)

  • XMLHttpRequest(XHR)是目前實現(xiàn)異步發(fā)送和接收數(shù)據(jù)最常用的技術。所有主流瀏覽器都提供了完善的支持。

<script>
   var url = '/data.php';
   var params = ['id=640202047','limit20'];
   var req = new XMLHttpRequest();
   req.onreadystatechange = function(){
      if(req.readyState == 4){
         //獲取響應頭信息
         var reqsponseHeaders = req.getAllResponseHeaders();
         //獲取數(shù)據(jù)
         var data = req.responseText;
      }
   }
   req.open('GET',url+'?'+params.join('&',true));
   //設置請求頭信息
   req.setRequestHeader('X-Requested-With','XMLHttpRequest');
   //發(fā)送請求
   req.send(null);
</script>
  • 瀏覽器對XHR進行跨域限制,不能通過XHR從外域請求數(shù)據(jù)。
  • XHR的POST和GET請求的選擇:對于那些不改變服務器狀態(tài),只獲取數(shù)據(jù)的請求,應該使用GET;但請求的URL長度接近或超過2048個字符時,應該選擇POST請求。
  • 動態(tài)腳本注入能夠跨域請求數(shù)據(jù),其原理就是使用JavaScript創(chuàng)建一個新的腳本標簽,設置它的src屬性為不同域的URL。
var scriptElement = document.createElement('script');
scriptElement.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName('head')[0].appendChild(scriptElement);
function jsonCallback(jsonString){
    var data = eval('(' + jsonString + ')');
}
jsonCallback({'status':1,"colors":['#fff','#000','#ff0000']});
  • 動態(tài)腳本注入的方式也有一些短板,不能合作請求的頭信息,只能通過GET方式,不能設置請求的超時處理或重試,并且可能帶來安全性問題。
  • multipart XHR(MXHR)是一項新技術,運行客戶端只用一個HTTP請求就可以從服務端獲取多個資源。
  • multipart XHR的原理是將資源(CSS/HTML/JavaScript/image)打包成一個由雙方約定格式的長字符串來傳輸,然后通過JavaScript根據(jù)mime-type類型來解析每個資源。
var req = new XMLHttpRequest();
var getLatesPacketInterval,lastLength = 0;
req.open('GET','rollup_image.php',true);
req.openreadystatechange = readyStateHander;
req.send(null);
function readyStateHander(){
    if(req.readyState == 3){
        //開始輪詢
       getLatestPacketInterval = window.setInterval(function(){
            getLaesPacket();
        },15);
    }

    if(req.readyState == 4){
        //停止輪詢
        clearInterval(getLatestPacketInterval);
        //獲取最后一個數(shù)據(jù)包
        getLaesPacket();
    }
}
function getLaesPacket(){
    var length = req.responseText.length;
    var packet = req.responseText.substring(lastLength,length);
    processPacket(packet);
    lastLenght = length;
}

readySate狀態(tài)為3時,啟動一個定時器,每15毫秒檢查一次響應中的新數(shù)據(jù)。數(shù)據(jù)片段會被收集起來,直到發(fā)現(xiàn)一個分隔符,然后就把遇到分隔符之前的數(shù)據(jù)作為一個完整的資源進行處理。(編寫健壯的MXHR代碼比較復雜,但卻值得研究)

  • MXHR最大的缺點就是以此種方式獲取的數(shù)據(jù)不能被瀏覽器緩存,比如通過MXHR獲取的CSS和圖片文件無法被緩存。

發(fā)送數(shù)據(jù)(Sending Data)

  • 當我們需要發(fā)送用戶操作日志或者需要記錄腳本錯誤時,我們并不關心接受數(shù)據(jù),只關系數(shù)據(jù)發(fā)送給服務器。此時有兩種廣泛使用的技術:XHRbeacons(信標)。
  • 雖然XHR主要用于從服務器獲取數(shù)據(jù),但同樣可以通過POST方式把數(shù)據(jù)發(fā)送服務器。
  • 案例:用XHR發(fā)送用戶統(tǒng)計信息
var url = '/data.php';
var params = ['id=640202047','limit20'];
var req = new XMLHttpRequest();
req.onreadystatechange = function(){
   if(req.readyState == 4){
      //成功
   }
};
//處理發(fā)送失敗的情況
req.onerror = fucntion(){
    setTimeout(arguments.callee,1000);
};
req.open('POST',url+'?'+params.join('&',true));
//設置請求頭信息
req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
req.setRequestHeader('Content-Length',params.length);
//發(fā)送請求
req.send(param.join('&'));
  • Beacons非常類似動態(tài)腳本注入,即用JavaScript創(chuàng)建一個Image對象,把src屬性設置為要請求的URL,URL中包含要上送的鍵值對數(shù)據(jù)。
var url = '/status_tracker.php';
var params = ['id=640202047','limit20'];
(new Image()).src = url + '?' + param.join(‘&’);
//注意:創(chuàng)建Image元素即可,不需要插入DOM
  • 使用圖片信標的方式向服務器發(fā)送數(shù)據(jù),是最快捷且最有效的。它的性能消耗極小,服務端的錯誤完全不會影響客戶端。(如果需要處理其錯誤的情況,也可以通過onloadonerror事件來監(jiān)聽)

數(shù)據(jù)格式(DataS Formats)

  • 選擇數(shù)據(jù)格式取決于具體的用途,不同的數(shù)據(jù)格式有不同的優(yōu)勢,有的解析更快,有點傳輸更快。
  • 當Ajax開始流行時,選擇了XML作為數(shù)據(jù)格式。XML結構嚴謹,具有極佳的通用性,幾乎所有的服務端語言都有支持XML操作的類庫。但XML比起其他格式更冗長,每個數(shù)據(jù)片段都依賴大量結構。而在JavaScript中,解析XML需要花費相當大的精力。
function parseXml(responseXML){
    var users = [];
    var userNodes = responseXML.getElementsByName('users');
    var node,usernameNodes.usernameNode,username,
     realnameNodes,realnameNode,realname,
     emailNodes,emailNode,email;
 for (var i=0,len=userNodes.length;i<len;i++) {
 //獲取節(jié)點
 node = userNodes[i];
 //初始化變量
 username = realname = email = '';
 //解析用戶名
 usernameNode = node.getElementsByTagName('username');
 if(usernameNode && usernameNodes[0]){
 usernameNode = usernameNodes[0];
 username = (usernameNodes.firstChild())?
 usernameNodes.firstChild():'';
 }
 //解析本名
 realnameNodes = node.getElementsByTagName('realname');
 if(realnameNode && realnameNodes[0]){
 realnameNode = realnameNodes[0];
 realname = (realnameNodes.firstChild())?
 realnameNodes.firstChild():'';
 }
 //解析郵箱地址
 emailNodes = node.getElementsByTagName('email');
 if(emailNode && emailNodes[0]){
 emailNode = emailNodes[0];
 email = (emailNodes.firstChild())?
 emailNodes.firstChild():'';
 }
 //放入數(shù)組
 user[i] = {
 id : node.getAtrribute('id'),
 username : username,
 realname : realname,
 email : email
 };
 }
}
  • 另外,通過XPath可以更方便的解析XML文檔。
  • JSON是一種使用JavaScript對象和數(shù)組字面量編寫的,輕量級且易于維護的數(shù)據(jù)格式。
  • 在JavaScript中,可以通過eval()方法進行解析JSON字符串
function parseXml(responseText){
    var users = [];
    var userArray = eval('(' + responseText + ')');
    for (var i=0,len=userArray.length;i<len;i++) {
     users[i] = {
     id : userArray[i][0],
     username : userArray[i][1],
     realname : userArray[i][2],
     email : userArray[i][3]
     }
    }
}
  • 和XML相比JSON有著更小的傳輸尺寸,解析速度更快。對于Web開發(fā)人員來說,JSON是高性能Ajax的基礎,尤其是使用動態(tài)腳本注入時。

  • 通常請求的數(shù)據(jù)需要被轉換成HTML以顯示到頁面上,而轉換的操作在服務器處理會快得多,所以有一種可考慮的技術,就是在服務器上構建好整個HTML再傳回客戶端。JavaScript再直接通過innerHTML屬性插入到頁面響應的位置。

document.getElementById('data-container').innerHTML = req.responseText;
  • 但HTML是一種臃腫的數(shù)據(jù)格式,HTML標簽可以相互嵌套,每個標簽還有id、class等屬性,甚至比XML更繁雜,同時導致傳輸數(shù)據(jù)量很大。

  • 除此之外,hi可以自定義數(shù)據(jù)格式。案例給出一個以分隔符組成的列表。

1:alice:Alice Smith:alice@alicesmih.com;
2:bob:Bob Jones:bob@bobjones.com;
3.dave:Dave Johnson:dave@daejohnson.com;
function parseXml(responseText){
    var users = [];
    var userEncoded = responseText.split(';');
    var userArray;
    for (var i=0,len=userEncoded .length;i<len;i++) {
        userArray = userEncoded[i].split(':');
     users[i] = {
     id : userArray[i][0],
     username : userArray[i][1],
     realname : userArray[i][2],
     email : userArray[i][3]
     };
    }
}

Ajax性能指南(Ajax Performance Guidelines)

  • 當選擇了合適的數(shù)據(jù)傳輸技術和數(shù)據(jù)格式后,就該開始考慮其他的優(yōu)化技術了。
緩存數(shù)據(jù)(Cache Data)
  • “最快的請求,就是沒有請求”,我們通過兩種方式避免不必要的請求:
    • 在服務端,設置HTTP頭信息以確保你的響應被瀏覽器緩存;
    • 在客戶端,把響應的數(shù)據(jù)存儲到本地,避免再次請求;
  • 除了使用GET方式發(fā)送請求之外,還需要設置正確的HTTP頭信息。指定Expires信息是最簡單的方式,指明瀏覽器應該緩存響應多久,而無須改變客戶端的任何代碼。
  • 除了依賴瀏覽器處理緩存外,還可以手動實現(xiàn)請求的緩存。
var localCache = {};    //緩存集合
var url = '/data.php';
function xhrRequest(url,callback){
 //檢查本地緩存的URL
 if(localCache[url]){
 callback.success(localCache[url]);
 return;
 }
 var req = createHhrObject();
 req.onerror = fucntion(){
     callback.error();
 };
 req.onreadystatechange = function(){
      if(req.readyState == 4){
       if(req.responseText === ''|| req.status == '404'){
       callback.error();
       }
        //存儲響應文本到本地緩存
        localCache[url] = req.responseText;
        callback.success(req.responseText);
      }
   };
   req.open('GET',url,true);
   req.send(null);
}
了解Ajax類庫的局限(Know the Limitations of Your Ajax Library)
  • 本來Ajax對象屏蔽了瀏覽器之間的差異是件好事,但卻使得開發(fā)者不能訪問XMLHttpRequest的完整功能。比如,監(jiān)聽readyState為3的狀態(tài),可以在一個較大的響應還沒有完全接收之間把它分段處理,可以實時處理響應片段。還可以訪問不同的readyStates狀態(tài),直接操作XHR對象減少函數(shù)開銷,進一步提升了性能。 (注意:但放棄使用Ajax類庫,可能會遇到古怪的瀏覽器兼容問題)
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容