提高AJAX客戶端響應速度

提高AJAX客戶端響應速度

(文:包一磊)

AJAX的出現極大的改變了Web應用客戶端的操作模式,它使的用戶可以在全心工作時不必頻繁的忍受那令人厭惡的頁面刷新。理論上AJAX技術在很大的程度上可以減少用戶操作的等待時間,同時節約網絡上的數據流量。而然,實際情況卻並不總是這樣。用戶時常會抱怨用了AJAX的系統響應速度反而降低了。

筆者從事AJAX方面的研發多年,參與開發了目前國內較爲成熟的AJAX平臺-dorado。根據筆者的經驗,導致這種結果的根本原因並不在AJAX。很多時候系統響應速度的降低都是由不夠合理的界面設計和不夠高效的編程習慣造成的。下面我們就來分析幾個AJAX開發過程中需要時刻注意的環節。

合理的使用客戶端編程和遠程過程調用。

客戶端的編程主要都是基於JavaScript的。而JavaScript是一種解釋型的編程語言,它的運行效率相對於Java等都要稍遜一籌。同時JavaScript又是運行在瀏覽器這樣一個嚴格受限的環境當中。因此開發人員對於哪些邏輯可以在客戶端執行應該有一個清醒的認識。

在實際的應用中究竟應該怎樣使用客戶端編程,這依賴於開發人員的經驗判斷。這裏很多問題是隻可意會的。由於篇幅有限,在這裏我們大致歸納出下面這幾個注意事項:

儘可能避免頻繁的使用遠程過程調用,例如避免在循環體中使用遠程過程調用。

如果可能的話儘可能使用AJAX方式的遠程過程調用(異步方式的遠程過程調用)。

避免將重量級的數據操作放置在客戶端。例如:大批量的數據複製操作、需要通過大量的數據遍歷完成的計算等。

改進對DOM對象的操作方式。

客戶端的編程中,對DOM對象的操作往往是最容易佔用CPU時間的。而對於DOM對象的操作,不同的編程方法之間的性能差異又往往是非常大的。

以下是三段運行結果完全相同的代碼,它們的作用是在網頁中創建一個10x1000的表格。然而它們的運行速度卻有着天壤之別。

/* 測試代碼1 - 耗時: 41*/

var table = document.createElement("TABLE");

document.body.appendChild(table);

for(var i = 0; i < 1000; i++){

 var row = table.insertRow(-1);

 for(var j = 0; j < 10; j++){

    var cell = objRow.insertCell(-1);

     cell.innerText = "( " + i + " , " + j + " )";

 }

}

/* 測試代碼2 - 耗時: 7.6 */

var table = document.getElementById("TABLE");

document.body.appendChild(table);

var tbody = document.createElement("TBODY");

table.appendChild(tbody);

for(var i = 0; i < 1000; i++){

 var row = document.createElement("TR");

 tbody.appendChild(row);

 for(var j = 0; j < 10; j++){

    var cell = document.createElement("TD");

     row.appendChild(cell);

     cell.innerText = "( " + i + " , " + j + " )";

 }

}

/* 測試代碼3 - 耗時: 1.26 */

var tbody = document.createElement("TBODY");

for(var i = 0; i < 1000; i++){  

 var row = document.createElement("TR");

       for(var j = 0; j < 10; j++){

    var cell = document.createElement("TD");

     cell.innerText = "( " + i + " , " + j + " )";

     row.appendChild(cell);

 }

 tbody.appendChild(row);

}

var table = document.getElementById("TABLE");

table.appendChild(tbody);

document.body.appendChild(table);

這裏的“測試代碼1”和“測試代碼2”之間的差別在於在創建表格單元時使用了不同的API方法。而“測試代碼2”和“測試代碼3之間的差別在於處理順序的略微不同。

“測試代碼1”和“測試代碼2”之間如此大的性能差別我們無從分析,目前所知的是insertRowinsertCellDHTML中表格特有的APIcreateElementappendChildW3C DOM的原生API。而前者應該是對後者的封裝。不過,我們並不能因此而得出結論認爲DOM的原生API總是優於對象特有的API。建議大家在需要頻繁調用某一API時,對其性能表現做一些基本的測試。

“測試代碼2”和“測試代碼3”之間的性能差異主要來自於他們的構建順序不同。“測試代碼2”的做法是首先創建最外層的<TABLE>對象,然後再在循環中依次創建<TR><TD>。而“測試代碼3”的做法是首先在內存中由內到外的構建好整個表格,最後再將它添加到網頁中。這樣做的目的是儘可能的減少瀏覽器重新計算頁面佈局的次數。每當我們將一個對象添加到網頁中時,瀏覽器都會嘗試對頁面中的控件的佈局進行重新計算。所以,如果我們能夠首先在內存中將整個要構造的對象全部創建好,然後再一次性的添加到網頁中。那麼,瀏覽器將只會做一次佈局的重計算。總結爲一句話那就是越晚執行appendChild越好。有時爲了提高運行效率,我們甚至可以考慮先使用removeChild將已存在的控件從頁面中移除,然後構造完成後再重新將其放置回頁面當中。

提高字符串累加的速度

在使用AJAX提交信息時,我可能常常需要拼裝一些比較大的字符串通過XmlHttp來完成POST提交。儘管提交這樣大的信息的做法看起來並不優雅,但有時我們可能不得不面對這樣的需求。那麼JavaScript中對字符串的累加速度如何呢?我們先來做下面的這個實驗。累加一個長度爲30000的字符串。

/* 測試代碼1 - 耗時: 14.325 */

var str = "";

for (var i = 0; i < 50000; i++) {

       str += "xxxxxx";

}

這段代碼耗時14.325秒,結果並不理想。現在我們將代碼改爲如下的形式:

/* 測試代碼2 - 耗時: 0.359 */

var str = "";

for (var i = 0; i < 100; i++) {

       var sub = "";

       for (var j = 0; j < 500; j++) {

              sub += "xxxxxx";

       }

       str += sub;

}

這段代碼耗時0.359秒!同樣的結果,我們做的只是首先拼裝一些較小的字符串然後再組裝成更大的字符串。這種做法可以有效的在字符串拼裝的後期減小內存複製的數據量。知道了這一原理之後我們還可以把上面的代碼進一步拆散以後進行測試。下面的代碼僅耗時0.140秒。

/* 測試代碼3 - 耗時: 0.140 */

var str = ""; 

for (var i1 = 0; i1 < 5; i1++) {

       var str1 = "";

       for (var i2 = 0; i2 < 10; i2++) {

              var str2 = "";

              for (var i3 = 0; i3 < 10; i3++) {

                     var str3 = "";

                     for (var i4 = 0; i4 < 10; i4++) {

                            var str4 = "";

                            for (var i5 = 0; i5 < 10; i5++) {

                                   str4 += "xxxxxx";

                            }

                            str3 += str4;

                     }

                     str2 += str3;

              }

              str1 += str2;      

       }

       str += str1; 

}

不過,上面這種做法也許並不是最好的!如果我們需要提交的信息是XML格式的(其實絕大多數情況下,我們都可以設法將要提交的信息組裝成XML格式),我們還能找到更高效更優雅的方法利用DOM對象爲我們組裝字符串。下面這段代買組裝一個長度爲950015的字符串僅須耗時0.890秒。

/* 利用DOM對象組裝信息 - 耗時: 0.890 */

var xmlDoc; 

if (browserType == BROWSER_IE) {

       xmlDoc = new ActiveXObject("Msxml.DOMDocument");

}

else {

       xmlDoc = document.createElement("DOM");

}

var root = xmlDoc.createElement("root");

for (var i = 0; i < 50000; i++) {

       var node = xmlDoc.createElement("data");

       if (browserType == BROWSER_IE) {

              node.text = "xxxxxx";

       }

       else {

              node.innerText = "xxxxxx";

       }

       root.appendChild(node);

}

xmlDoc.appendChild(root);

var str;

if (browserType == BROWSER_IE) {

       str = xmlDoc.xml;

}

else {

       str = xmlDoc.innerHTML;

}

避免DOM對象的內存泄漏。

關於IEDOM對象的內存泄露是一個常常被開發人員忽略的問題。然而它帶來的後果卻是非常嚴重的!它會導致IE的內存佔用量持續上升,並且瀏覽器的整體運行速度明顯下降。對於一些泄露比較嚴重的網頁,甚至只要刷新幾次,運行速度就會降低一倍。

比較常見的內存泄漏的模型有“循環引用模型”、“閉包函數模型”和“DOM插入順序模型”,對於前兩種泄漏模型,我們都可以通過在網頁析構時解除引用的方式來避免。而對於“DOM插入順序模型”則需要通過改變一些慣有的編程習慣的方式來避免。

有關內存泄漏的模型的更多介紹可以通過Google很快的查到,本文不做過多的闡述。不過,這裏我向您推薦一個可用於查找和分析網頁內存泄露的小工具Drip,目前的較新版本是0.5,下載地址是http://outofhanwell.com/ieleak/index.php

複雜頁面的分段裝載和初始化

對系統當中某些確實比較複雜而又不便使用IFrame的界面,我們可以對其實施分段裝載。例如對於多頁標籤的界面,我們可以首先下載和初始化多頁標籤的默認頁,然後利用AJAHasynchronous JavaScript and HTML)技術來異步的裝載其他標籤頁中的內容。這樣就能保證界面可以在第一時間首先展現給用戶。把整個複雜界面的裝載過程分散到用戶的操作過程當中。

利用GZIP壓縮網絡流量。

除了上面提到的這些代碼級的改良之外,我們還可以利用GZIP來有效的降低網絡流量。目前常見的主流瀏覽器已經全部支持GZIP算法,我們往往只需要編寫少量的代碼就可以支持GZIP了。例如在J2EE中我們可以在Filter中通過下面的代碼來判斷客戶端瀏覽器是否支持GZIP算法,然後根據需要利用java.util.zip.GZIPOutputStream來實現GZIP的輸出。

/* 判斷瀏覽器對GZIP支持方式的代碼 */

private static String getGZIPEncoding(HttpServletRequest request) {

 String acceptEncoding = request.getHeader("Accept-Encoding");

 if (acceptEncoding == null) return null;

 acceptEncoding = acceptEncoding.toLowerCase();

 if (acceptEncoding.indexOf("x-gzip") >= 0) return "x-gzip";

 if (acceptEncoding.indexOf("gzip") >= 0) return "gzip";

 return null;

}

一般而言,GZIP對於HTMLJSP的壓縮比可以達到80%左右,而它造成的服務端和客戶端的性能損耗幾乎是可以忽略的。結合其他因素,支持GZIP的網站有可能爲我們節約50%的網絡流量。因此GZIP的使用可以爲那些網絡環境不是特別好的應用帶來顯著的性能提升。使用Http的監視工具Fiddler可以方便的檢測出網頁在使用GZIP前後的通訊數據量。Fiddler的下載地址是http://www.fiddlertool.com/fiddler/

關於Web應用的性能優化其實是一個非常大的話題。本文由於篇幅有限,只能涉及其中的幾個細節,並且也無法將這些細節的優化方式全面的展現給大家。期望本文能夠引起大家對Web應用尤其是客戶端性能優化的充分重視。畢竟服務端編程技巧已爲大家熟知多年,在服務端挖掘性能的潛力已經不大了。而在客戶端的方法改進往往能夠得到令人驚奇的性能提升。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章