AJAX的出現極大的改變了Web應用客戶端的操做模式,它使的用戶能夠在全心工做時沒必要頻繁的忍受那使人厭惡的頁面刷新。理論上AJAX技術在很大的程度上能夠減小用戶操做的等待時間,同時節約網絡上的數據流量。而然,實際狀況卻並不老是這樣。用戶時常會抱怨用了AJAX的系統響應速度反而下降了。php
筆者從事AJAX方面的研發多年,參與開發了目前國內較爲成熟的AJAX平臺-dorado。根據筆者的經驗,致使這種結果的根本緣由並不在AJAX。不少時候系統響應速度的下降都是由不夠合理的界面設計和不夠高效的編程習慣形成的。下面咱們就來分析幾個AJAX開發過程當中須要時刻注意的環節。html
n 合理的使用客戶端編程和遠程過程調用。java
客戶端的編程主要都是基於JavaScript的。而JavaScript是一種解釋型的編程語言,它的運行效率相對於Java等都要稍遜一籌。同時JavaScript又是運行在瀏覽器這樣一個嚴格受限的環境當中。所以開發人員對於哪些邏輯能夠在客戶端執行應該有一個清醒的認識。node
在實際的應用中究竟應該怎樣使用客戶端編程,這依賴於開發人員的經驗判斷。這裏不少問題是隻可意會的。因爲篇幅有限,在這裏咱們大體概括出下面這幾個注意事項:算法
u 儘量避免頻繁的使用遠程過程調用,例如避免在循環體中使用遠程過程調用。編程
u 若是可能的話儘量使用AJAX方式的遠程過程調用(異步方式的遠程過程調用)。瀏覽器
u 避免將重量級的數據操做放置在客戶端。例如:大批量的數據複製操做、須要經過大量的數據遍歷完成的計算等。網絡
n 改進對DOM對象的操做方式。閉包
客戶端的編程中,對DOM對象的操做每每是最容易佔用CPU時間的。而對於DOM對象的操做,不一樣的編程方法之間的性能差別又每每是很是大的。app
如下是三段運行結果徹底相同的代碼,它們的做用是在網頁中建立一個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」之間如此大的性能差異咱們無從分析,目前所知的是insertRow和insertCell是DHTML中表格特有的API,createElement和appendChild是W3C DOM的原生API。而前者應該是對後者的封裝。不過,咱們並不能所以而得出結論認爲DOM的原生API老是優於對象特有的API。建議你們在須要頻繁調用某一API時,對其性能表現作一些基本的測試。
「測試代碼2」和「測試代碼3」之間的性能差別主要來自於他們的構建順序不一樣。「測試代碼2」的作法是首先建立最外層的<TABLE>對象,而後再在循環中依次建立<TR>和<TD>。而「測試代碼3」的作法是首先在內存中由內到外的構建好整個表格,最後再將它添加到網頁中。這樣作的目的是儘量的減小瀏覽器從新計算頁面佈局的次數。每當咱們將一個對象添加到網頁中時,瀏覽器都會嘗試對頁面中的控件的佈局進行從新計算。因此,若是咱們可以首先在內存中將整個要構造的對象所有建立好,而後再一次性的添加到網頁中。那麼,瀏覽器將只會作一次佈局的重計算。總結爲一句話那就是越晚執行appendChild越好。有時爲了提升運行效率,咱們甚至能夠考慮先使用removeChild將已存在的控件從頁面中移除,而後構造完成後再從新將其放置回頁面當中。
n 提升字符串累加的速度
在使用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; } |
n 避免DOM對象的內存泄漏。
關於IE中DOM對象的內存泄露是一個經常被開發人員忽略的問題。然而它帶來的後果倒是很是嚴重的!它會致使IE的內存佔用量持續上升,而且瀏覽器的總體運行速度明顯降低。對於一些泄露比較嚴重的網頁,甚至只要刷新幾回,運行速度就會下降一倍。
比較常見的內存泄漏的模型有「循環引用模型」、「閉包函數模型」和「DOM插入順序模型」,對於前兩種泄漏模型,咱們均可以經過在網頁析構時解除引用的方式來避免。而對於「DOM插入順序模型」則須要經過改變一些慣有的編程習慣的方式來避免。
有關內存泄漏的模型的更多介紹能夠經過Google很快的查到,本文不作過多的闡述。不過,這裏我向您推薦一個可用於查找和分析網頁內存泄露的小工具—Drip,目前的較新版本是0.5,下載地址是http://outofhanwell.com/ieleak/index.php
n 複雜頁面的分段裝載和初始化
對系統當中某些確實比較複雜而又不便使用IFrame的界面,咱們能夠對其實施分段裝載。例如對於多頁標籤的界面,咱們能夠首先下載和初始化多頁標籤的默認頁,而後利用AJAH(asynchronous JavaScript and HTML)技術來異步的裝載其餘標籤頁中的內容。這樣就能保證界面能夠在第一時間首先展示給用戶。把整個複雜界面的裝載過程分散到用戶的操做過程中。
n 利用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對於HTML、JSP的壓縮比能夠達到80%左右,而它形成的服務端和客戶端的性能損耗幾乎是能夠忽略的。結合其餘因素,支持GZIP的網站有可能爲咱們節約50%的網絡流量。所以GZIP的使用能夠爲那些網絡環境不是特別好的應用帶來顯著的性能提高。使用Http的監視工具Fiddler能夠方便的檢測出網頁在使用GZIP先後的通信數據量。Fiddler的下載地址是http://www.fiddlertool.com/fiddler/