筆者從事AJAX 方面的研發(fā)多年,參與開(kāi)發(fā)了目前國內較為成熟的AJAX平臺 -dorado 。根據筆者的經(jīng)驗,導致這種結果的根本原因并不在A(yíng)JAX 。很多時(shí)候系統響應速度的降低都是由不夠合理的界面設計和不夠高效的編程習慣造成的。下面我們就來(lái)分析幾個(gè) AJAX 開(kāi)發(fā)過(guò)程中需要時(shí)刻注意的環(huán)節。
<!-- [if !supportLists]-->n <!-- [endif]-->合理的使用AJAX客戶(hù)端編程和遠程過(guò)程調用。
AJAX客戶(hù)端的編程主要都是基于 JavaScript 的。而 JavaScript 是一種解釋型的編程語(yǔ)言,它的運行效率相對于 Java 等都要稍遜一籌。同時(shí) JavaScript 又是運行在瀏覽器這樣一個(gè)嚴格受限的環(huán)境當中。因此開(kāi)發(fā)人員對于哪些邏輯可以在客戶(hù)端執行應該有一個(gè)清醒的認識。
在實(shí)際的應用中究竟應該怎樣使用 客戶(hù)端編程,這依賴(lài)于開(kāi)發(fā)人員的經(jīng)驗判斷。這里很多問(wèn)題是只可意會(huì )的。由于篇幅有限,在這里我們大致歸納出下面這幾個(gè)注意事項:
<!-- [if !supportLists]-->u <!-- [endif]-->盡可能避免頻繁的使用遠程過(guò)程調用,例如避免在循環(huán)體中使用遠程過(guò)程調用。
<!-- [if !supportLists]-->u <!-- [endif]-->如果可能的話(huà)盡可能使用 AJAX 方式的遠程過(guò)程調用(異步方式的遠程過(guò)程調用)。
<!-- [if !supportLists]-->u <!-- [endif]-->避免將重量級的數據操作放置在 客戶(hù)端。例如:大批量的數據復制操作、需要通過(guò)大量的數據遍歷完成的計算等。
<!-- [if !supportLists]-->n <!-- [endif]-->改進(jìn)對 DOM 對象的操作方式。
客戶(hù)端的編程中,對 DOM 對象的操作往往是最容易占用 CPU 時(shí)間的。而對于 DOM 對象的操作,不同的編程方法之間的性能差異又往往是非常大的。
以下是三段運行結果完全相同的代碼,它們的作用是在網(wǎng)頁(yè)中創(chuàng )建一個(gè) 10x1000 的表格。然而它們的運行速度卻有著(zhù)天壤之別。
- /* 測試代碼 1 - 耗時(shí) : 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 - 耗時(shí) : 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 - 耗時(shí) : 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 ”之間的差別在于在創(chuàng )建表格單元時(shí)使用了不同的 API 方法。而“測試代碼 2 ”和“測試代碼 3 ” 之間的差別在于處理順序的略微不同。
“測試代碼 1 ”和“測試代碼 2 ”之間如此大的性能差別我們無(wú)從分析,目前所知的是 insertRow 和 insertCell 是 DHTML 中表格特有的 API , createElement 和 appendChild 是 W3C DOM 的原生 API 。而前者應該是對后者的封裝。不過(guò),我們并不能因此而得出結論認為 DOM 的原生 API 總是優(yōu)于對象特有的 API 。建議大家在需要頻繁調用某一 API 時(shí),對其性能表現做一些基本的測試。
“測試代碼 2 ”和“測試代碼 3 ”之間的性能差異主要來(lái)自于他們的構建順序不同。“測試代碼 2 ”的做法是首先創(chuàng )建最外層的 <TABLE> 對象,然后再在循環(huán)中依次創(chuàng )建 <TR> 和 <TD> 。而“測試代碼 3 ”的做法是首先在內存中由內到外的構建好整個(gè)表格,最后再將它添加到網(wǎng)頁(yè)中。這樣做的目的是盡可能的減少瀏覽器重新計算頁(yè)面布局的次數。每當我們將一個(gè)對象添加到網(wǎng)頁(yè)中時(shí),瀏覽器都會(huì )嘗試對頁(yè)面中的控件的布局進(jìn)行重新計算。所以,如果我們能夠首先在內存中將整個(gè)要構造的對象全部創(chuàng )建好,然后再一次性的添加到網(wǎng)頁(yè)中。那么,瀏覽器將只會(huì )做一次布局的重計算 ??偨Y為一句話(huà)那就是越晚執行 appendChild 越好。有時(shí)為了提高運行效率,我們甚至可以考慮先使用 removeChild 將已存在的控件從頁(yè)面中移除,然后構造完成后再重新將其放置回頁(yè)面當中。
<!-- [if !supportLists]-->n <!-- [endif]-->提高字符串累加的速度
在使用 AJAX 提交信息時(shí),我可能常常需要拼裝一些比較大的字符串通過(guò) XmlHttp 來(lái)完成 POST 提交。盡管提交這樣大的信息的做法看起來(lái)并不優(yōu)雅,但有時(shí)我們可能不得不面對這樣的需求。那么 JavaScript 中對字符串的累加速度如何呢?我們先來(lái)做下面的這個(gè)實(shí)驗。累加一個(gè)長(cháng)度為 30000 的字符串。
- /* 測試代碼 1 - 耗時(shí) : 14.325 秒 */
- var str = "";
- for (var i = 0; i < 50000; i++) {
- str += "xxxxxx";
- }
這段代碼耗時(shí) 14.325 秒,結果并不理想?,F在我們將代碼改為如下的形式:
- /* 測試代碼 2 - 耗時(shí) : 0.359 秒 */
- var str = "";
- for (var i = 0; i < 100; i++) {
- var sub = "";
- for (var j = 0; j < 500; j++) {
- sub += "xxxxxx";
- }
- str += sub;
- }
這段代碼耗時(shí) 0.359 秒!同樣的結果,我們做的只是首先拼裝一些較小的字符串然后再組裝成更大的字符串。這種做法可以有效的在字符串拼裝的后期減小內存復制的數據量。知道了這一原理之后我們還可以把上面的代碼進(jìn)一步拆散以后進(jìn)行測試。下面的代碼僅耗時(shí) 0.140 秒。
- /* 測試代碼 3 - 耗時(shí) : 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;
- }
不過(guò),上面這種做法也許并不是最好的!如果我們需要提交的信息是 XML 格式的(其實(shí)絕大多數情況下,我們都可以設法將要提交的信息組裝成 XML 格式),我們還能找到更高效更優(yōu)雅的方法 — 利用 DOM 對象為我們組裝字符串。下面這段代買(mǎi)組裝一個(gè)長(cháng)度為 950015 的字符串僅須耗時(shí) 0.890 秒。
- /* 利用 DOM 對象組裝信息 - 耗時(shí) : 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;
- }
- <!-- [if !supportLists]-->n
<!-- [endif]-->避免 DOM 對象的內存泄漏。
關(guān)于 IE 中 DOM 對象的內存泄露是一個(gè)常常被開(kāi)發(fā)人員忽略的問(wèn)題。然而它帶來(lái)的后果卻是非常嚴重的!它會(huì )導致 IE 的內存占用量持續上升,并且瀏覽器的整體運行速度明顯下降。對于一些泄露比較嚴重的網(wǎng)頁(yè),甚至只要刷新幾次,運行速度就會(huì )降低一倍。
比較常見(jiàn)的內存泄漏的模型有“ 循環(huán)引用 模型”、“ 閉包函數 模型”和“ DOM 插入順序模型” , 對于前兩種泄漏模型,我們都可以通過(guò)在網(wǎng)頁(yè)析構時(shí)解除引用的方式來(lái)避免。而對于“ DOM 插入順序模型”則需要通過(guò)改變一些慣有的編程習慣的方式來(lái)避免。
有關(guān)內存泄漏的模型的更多介紹可以通過(guò) Google 很快的查到,本文不做過(guò)多的闡述。不過(guò),這里我向您推薦一個(gè)可用于查找和分析網(wǎng)頁(yè)內存泄露的小工具 — Drip ,目前的較新版本是 0.5 ,下載地址是 http://outofhanwell.com/ieleak/index.php
<!-- [if !supportLists]-->n <!-- [endif]-->復雜頁(yè)面的分段裝載和初始化
對系統當中某些確實(shí)比較復雜而又不便使用 IFrame 的界面,我們可以對其實(shí)施分段裝載。例如對于多頁(yè)標簽的界面,我們可以首先下載和初始化多頁(yè)標簽的默認頁(yè),然后利用 AJAH ( asynchronous JavaScript and HTML )技術(shù)來(lái)異步的裝載其他標簽頁(yè)中的內容。這樣就能保證界面可以在第一時(shí)間首先展現給用戶(hù)。把整個(gè)復雜界面的裝載過(guò)程分散到用戶(hù)的操作過(guò)程當中。
<!-- [if !supportLists]-->n <!-- [endif]-->利用 GZIP 壓縮網(wǎng)絡(luò )流量。
除了上面提到的這些代碼級的改良之外,我們還可以利用 GZIP 來(lái)有效的降低網(wǎng)絡(luò )流量。目前常見(jiàn)的主流瀏覽器已經(jīng)全部支持 GZIP 算法,我們往往只需要編寫(xiě)少量的代碼就可以支持 GZIP 了。例如在 J2EE 中我們可以在 Filter 中通過(guò)下面的代碼來(lái)判斷客戶(hù)端瀏覽器是否支持 GZIP 算法,然后根據需要利用 java.util.zip.GZIPOutputStream 來(lái)實(shí)現 GZIP 的輸出。
- /* 判斷瀏覽器對 GZIP 支持方式的代碼 */
- private static String getGZIPEncoding(HttpServletRequest request) {
- String acceptEncoding = request.getHeader("Accept-Encoding");
- if (acceptEncoding == null) return null;
- acceptEncodingacceptEncoding = acceptEncoding.toLowerCase();
- if (acceptEncoding.indexOf("x-gzip") >= 0) return "x-gzip";
- if (acceptEncoding.indexOf("gzip") >= 0) return "gzip";
- return null;
- }
一般而言, GZIP 對于 HTML 、 JSP 的壓縮比可以達到 80% 左右,而它造成的服務(wù)端和客戶(hù)端的性能損耗幾乎是可以忽略的。結合其他因素,支持 GZIP 的網(wǎng)站有可能為我們節約 50% 的網(wǎng)絡(luò )流量。因此 GZIP 的使用可以為那些網(wǎng)絡(luò )環(huán)境不是特別好的應用帶來(lái)顯著(zhù)的性能提升。使用 Http 的監視工具 Fiddler 可以方便的檢測出網(wǎng)頁(yè)在使用 GZIP 前后的通訊數據量。 Fiddler 的下載地址是 http://www.fiddlertool.com/fiddler/
關(guān)于 Web 應用的性能優(yōu)化其實(shí)是一個(gè)非常大的話(huà)題。本文由于篇幅有限,只能涉及其中的幾個(gè)細節,并且也無(wú)法將這些細節的優(yōu)化方式全面的展現給大家。期望本文能夠引起大家對 Web 應用尤其是客戶(hù)端性能優(yōu)化的充分重視。畢竟服務(wù)端編程技巧已為大家熟知多年,在服務(wù)端挖掘性能的潛力已經(jīng)不大了。而在客戶(hù)端的方法改進(jìn)往往能夠得到令人驚奇的性能提升
聯(lián)系客服