我是一棵無人需要的小草

          淺談如何提高AJAX客戶端響應(yīng)速度【轉(zhuǎn)】

            AJAX 的出現(xiàn)極大的改變了 Web 應(yīng)用客戶端的操作模式,它使的用戶可以在全心工作時(shí)不必頻繁的忍受那令人厭惡的頁(yè)面刷新。理論上 AJAX 技術(shù)在很大的程度上可以減少用戶操作的等待時(shí)間,同時(shí)節(jié)約網(wǎng)絡(luò)上的數(shù)據(jù)流量。而然,實(shí)際情況卻并不總是這樣。用戶時(shí)常會(huì)抱怨用了 AJAX 的系統(tǒng)響應(yīng)速度反而降低了。本文將談?wù)勅绾翁岣唔憫?yīng)速度。

             

            筆者從事AJAX 方面的研發(fā)多年,參與開發(fā)了目前國(guó)內(nèi)較為成熟的AJAX平臺(tái) -dorado 。根據(jù)筆者的經(jīng)驗(yàn),導(dǎo)致這種結(jié)果的根本原因并不在AJAX 。很多時(shí)候系統(tǒng)響應(yīng)速度的降低都是由不夠合理的界面設(shè)計(jì)和不夠高效的編程習(xí)慣造成的。下面我們就來分析幾個(gè) AJAX 開發(fā)過程中需要時(shí)刻注意的環(huán)節(jié)。

            <!-- [if !supportLists]-->n         <!-- [endif]-->合理的使用AJAX客戶端編程和遠(yuǎn)程過程調(diào)用。

            AJAX客戶端的編程主要都是基于 JavaScript 的。而 JavaScript 是一種解釋型的編程語言,它的運(yùn)行效率相對(duì)于 Java 等都要稍遜一籌。同時(shí) JavaScript 又是運(yùn)行在瀏覽器這樣一個(gè)嚴(yán)格受限的環(huán)境當(dāng)中。因此開發(fā)人員對(duì)于哪些邏輯可以在客戶端執(zhí)行應(yīng)該有一個(gè)清醒的認(rèn)識(shí)。

            在實(shí)際的應(yīng)用中究竟應(yīng)該怎樣使用 客戶端編程,這依賴于開發(fā)人員的經(jīng)驗(yàn)判斷。這里很多問題是只可意會(huì)的。由于篇幅有限,在這里我們大致歸納出下面這幾個(gè)注意事項(xiàng):

            <!-- [if !supportLists]-->u       <!-- [endif]-->盡可能避免頻繁的使用遠(yuǎn)程過程調(diào)用,例如避免在循環(huán)體中使用遠(yuǎn)程過程調(diào)用。

            <!-- [if !supportLists]-->u       <!-- [endif]-->如果可能的話盡可能使用 AJAX 方式的遠(yuǎn)程過程調(diào)用(異步方式的遠(yuǎn)程過程調(diào)用)。

            <!-- [if !supportLists]-->u       <!-- [endif]-->避免將重量級(jí)的數(shù)據(jù)操作放置在 客戶端。例如:大批量的數(shù)據(jù)復(fù)制操作、需要通過大量的數(shù)據(jù)遍歷完成的計(jì)算等。

            <!-- [if !supportLists]-->n         <!-- [endif]-->改進(jìn)對(duì) DOM 對(duì)象的操作方式。

            客戶端的編程中,對(duì) DOM 對(duì)象的操作往往是最容易占用 CPU 時(shí)間的。而對(duì)于 DOM 對(duì)象的操作,不同的編程方法之間的性能差異又往往是非常大的。

            以下是三段運(yùn)行結(jié)果完全相同的代碼,它們的作用是在網(wǎng)頁(yè)中創(chuàng)建一個(gè) 10x1000 的表格。然而它們的運(yùn)行速度卻有著天壤之別。

                    
            1. /* 測(cè)試代碼 1 - 耗時(shí) : 41 秒 */   
            2. var table = document.createElement("TABLE");   
            3. document.body.appendChild(table);   
            4. for(var i = 0; i < 1000; i++){   
            5.   var row = table.insertRow(-1);   
            6.   for(var j = 0; j < 10; j++){   
            7.     var cell = objRow.insertCell(-1);   
            8.       cell.innerText = "( " + i + " , " + j + " )";   
            9.   }   
            10. }   
            11. /* 測(cè)試代碼 2 - 耗時(shí) : 7.6 秒 */   
            12. var table = document.getElementById("TABLE");   
            13. document.body.appendChild(table);   
            14. var tbody = document.createElement("TBODY");   
            15. table.appendChild(tbody);   
            16. for(var i = 0; i < 1000; i++){   
            17.   var row = document.createElement("TR");   
            18.   tbody.appendChild(row);   
            19.   for(var j = 0; j < 10; j++){   
            20.     var cell = document.createElement("TD");   
            21.       row.appendChild(cell);   
            22.       cell.innerText = "( " + i + " , " + j + " )";   
            23.   }   
            24. }   
            25. /* 測(cè)試代碼 3 - 耗時(shí) : 1.26 秒 */   
            26. var tbody = document.createElement("TBODY");   
            27. for(var i = 0; i < 1000; i++){     
            28.   var row = document.createElement("TR");   
            29.        for(var j = 0; j < 10; j++){   
            30.     var cell = document.createElement("TD");   
            31.       cell.innerText = "( " + i + " , " + j + " )";   
            32.       row.appendChild(cell);   
            33.   }   
            34.   tbody.appendChild(row);   
            35. }   
            36. var table = document.getElementById("TABLE");   
            37. table.appendChild(tbody);   
            38. document.body.appendChild(table);  

            這里的“測(cè)試代碼 1 ”和“測(cè)試代碼 2 ”之間的差別在于在創(chuàng)建表格單元時(shí)使用了不同的 API 方法。而“測(cè)試代碼 2 ”和“測(cè)試代碼 3 ” 之間的差別在于處理順序的略微不同。

            “測(cè)試代碼 1 ”和“測(cè)試代碼 2 ”之間如此大的性能差別我們無從分析,目前所知的是 insertRow 和 insertCell 是 DHTML 中表格特有的 API , createElement 和 appendChild 是 W3C DOM 的原生 API 。而前者應(yīng)該是對(duì)后者的封裝。不過,我們并不能因此而得出結(jié)論認(rèn)為 DOM 的原生 API 總是優(yōu)于對(duì)象特有的 API 。建議大家在需要頻繁調(diào)用某一 API 時(shí),對(duì)其性能表現(xiàn)做一些基本的測(cè)試。

            “測(cè)試代碼 2 ”和“測(cè)試代碼 3 ”之間的性能差異主要來自于他們的構(gòu)建順序不同。“測(cè)試代碼 2 ”的做法是首先創(chuàng)建最外層的 <TABLE> 對(duì)象,然后再在循環(huán)中依次創(chuàng)建 <TR> 和 <TD> 。而“測(cè)試代碼 3 ”的做法是首先在內(nèi)存中由內(nèi)到外的構(gòu)建好整個(gè)表格,最后再將它添加到網(wǎng)頁(yè)中。這樣做的目的是盡可能的減少瀏覽器重新計(jì)算頁(yè)面布局的次數(shù)。每當(dāng)我們將一個(gè)對(duì)象添加到網(wǎng)頁(yè)中時(shí),瀏覽器都會(huì)嘗試對(duì)頁(yè)面中的控件的布局進(jìn)行重新計(jì)算。所以,如果我們能夠首先在內(nèi)存中將整個(gè)要構(gòu)造的對(duì)象全部創(chuàng)建好,然后再一次性的添加到網(wǎng)頁(yè)中。那么,瀏覽器將只會(huì)做一次布局的重計(jì)算 。總結(jié)為一句話那就是越晚執(zhí)行 appendChild 越好。有時(shí)為了提高運(yùn)行效率,我們甚至可以考慮先使用 removeChild 將已存在的控件從頁(yè)面中移除,然后構(gòu)造完成后再重新將其放置回頁(yè)面當(dāng)中。

            <!-- [if !supportLists]-->n         <!-- [endif]-->提高字符串累加的速度

            在使用 AJAX 提交信息時(shí),我可能常常需要拼裝一些比較大的字符串通過 XmlHttp 來完成 POST 提交。盡管提交這樣大的信息的做法看起來并不優(yōu)雅,但有時(shí)我們可能不得不面對(duì)這樣的需求。那么 JavaScript 中對(duì)字符串的累加速度如何呢?我們先來做下面的這個(gè)實(shí)驗(yàn)。累加一個(gè)長(zhǎng)度為 30000 的字符串。

                    
            1. /* 測(cè)試代碼 1 - 耗時(shí) : 14.325 秒 */    
            2. var str = "";   
            3. for (var i = 0; i < 50000; i++) {   
            4.        str += "xxxxxx";   
            5. }  

            這段代碼耗時(shí) 14.325 秒,結(jié)果并不理想。現(xiàn)在我們將代碼改為如下的形式:

                    
            1. /* 測(cè)試代碼 2 - 耗時(shí) : 0.359 秒 */    
            2. var str = "";   
            3. for (var i = 0; i < 100; i++) {   
            4.        var sub = "";   
            5.        for (var j = 0; j < 500; j++) {   
            6.               sub += "xxxxxx";   
            7.        }   
            8.        str += sub;   
            9. }  

            這段代碼耗時(shí) 0.359 秒!同樣的結(jié)果,我們做的只是首先拼裝一些較小的字符串然后再組裝成更大的字符串。這種做法可以有效的在字符串拼裝的后期減小內(nèi)存復(fù)制的數(shù)據(jù)量。知道了這一原理之后我們還可以把上面的代碼進(jìn)一步拆散以后進(jìn)行測(cè)試。下面的代碼僅耗時(shí) 0.140 秒。

                    
            1. /* 測(cè)試代碼 3 - 耗時(shí) : 0.140 秒 */   
            2. var str = "";    
            3. for (var i1 = 0; i1 < 5; i1++) {   
            4.        var str1 = "";   
            5.        for (var i2 = 0; i2 < 10; i2++) {   
            6.               var str2 = "";   
            7.               for (var i3 = 0; i3 < 10; i3++) {   
            8.                      var str3 = "";   
            9.                      for (var i4 = 0; i4 < 10; i4++) {   
            10.                             var str4 = "";   
            11.                             for (var i5 = 0; i5 < 10; i5++) {   
            12.                                    str4 += "xxxxxx";   
            13.                             }   
            14.                             str3 += str4;   
            15.                      }   
            16.                      str2 += str3;   
            17.               }   
            18.               str1 += str2;   
            19.        }   
            20.        str += str1;     
            21. }  

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

                    
            1. /* 利用 DOM 對(duì)象組裝信息 - 耗時(shí) : 0.890 秒 */   
            2. var xmlDoc;     
            3. if (browserType == BROWSER_IE) {   
            4.        xmlDoc = new ActiveXObject("Msxml.DOMDocument");   
            5. }   
            6. else {   
            7.        xmlDoc = document.createElement("DOM");   
            8. }   
            9. var root = xmlDoc.createElement("root");   
            10. for (var i = 0; i < 50000; i++) {   
            11.        var node = xmlDoc.createElement("data");   
            12.        if (browserType == BROWSER_IE) {   
            13.               node.text = "xxxxxx";   
            14.        }   
            15.        else {   
            16.               node.innerText = "xxxxxx";   
            17.        }   
            18.        root.appendChild(node);   
            19. }   
            20. xmlDoc.appendChild(root);   
            21. var str;   
            22. if (browserType == BROWSER_IE) {   
            23.        str = xmlDoc.xml;   
            24. }   
            25. else {   
            26.        str = xmlDoc.innerHTML;   
            27. }   
            28. <!-- [if !supportLists]-->n   

            <!-- [endif]-->避免 DOM 對(duì)象的內(nèi)存泄漏。

            關(guān)于 IE 中 DOM 對(duì)象的內(nèi)存泄露是一個(gè)常常被開發(fā)人員忽略的問題。然而它帶來的后果卻是非常嚴(yán)重的!它會(huì)導(dǎo)致 IE 的內(nèi)存占用量持續(xù)上升,并且瀏覽器的整體運(yùn)行速度明顯下降。對(duì)于一些泄露比較嚴(yán)重的網(wǎng)頁(yè),甚至只要刷新幾次,運(yùn)行速度就會(huì)降低一倍。

            比較常見的內(nèi)存泄漏的模型有“ 循環(huán)引用 模型”、“ 閉包函數(shù) 模型”和“ DOM 插入順序模型” , 對(duì)于前兩種泄漏模型,我們都可以通過在網(wǎng)頁(yè)析構(gòu)時(shí)解除引用的方式來避免。而對(duì)于“ DOM 插入順序模型”則需要通過改變一些慣有的編程習(xí)慣的方式來避免。

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

            <!-- [if !supportLists]-->n         <!-- [endif]-->復(fù)雜頁(yè)面的分段裝載和初始化

            對(duì)系統(tǒng)當(dāng)中某些確實(shí)比較復(fù)雜而又不便使用 IFrame 的界面,我們可以對(duì)其實(shí)施分段裝載。例如對(duì)于多頁(yè)標(biāo)簽的界面,我們可以首先下載和初始化多頁(yè)標(biāo)簽的默認(rèn)頁(yè),然后利用 AJAH ( asynchronous JavaScript and HTML )技術(shù)來異步的裝載其他標(biāo)簽頁(yè)中的內(nèi)容。這樣就能保證界面可以在第一時(shí)間首先展現(xiàn)給用戶。把整個(gè)復(fù)雜界面的裝載過程分散到用戶的操作過程當(dāng)中。

            <!-- [if !supportLists]-->n         <!-- [endif]-->利用 GZIP 壓縮網(wǎng)絡(luò)流量。

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

                    
            1. /* 判斷瀏覽器對(duì) GZIP 支持方式的代碼 */   
            2. private static String getGZIPEncoding(HttpServletRequest request) {   
            3.   String acceptEncoding = request.getHeader("Accept-Encoding");   
            4.   if (acceptEncoding == null) return null;   
            5.   acceptEncodingacceptEncoding = acceptEncoding.toLowerCase();   
            6.   if (acceptEncoding.indexOf("x-gzip") >= 0) return "x-gzip";   
            7.   if (acceptEncoding.indexOf("gzip") >= 0) return "gzip";   
            8.   return null;   

            一般而言, GZIP 對(duì)于 HTML 、 JSP 的壓縮比可以達(dá)到 80% 左右,而它造成的服務(wù)端和客戶端的性能損耗幾乎是可以忽略的。結(jié)合其他因素,支持 GZIP 的網(wǎng)站有可能為我們節(jié)約 50% 的網(wǎng)絡(luò)流量。因此 GZIP 的使用可以為那些網(wǎng)絡(luò)環(huán)境不是特別好的應(yīng)用帶來顯著的性能提升。使用 Http 的監(jiān)視工具 Fiddler 可以方便的檢測(cè)出網(wǎng)頁(yè)在使用 GZIP 前后的通訊數(shù)據(jù)量。 Fiddler 的下載地址是 http://www.fiddlertool.com/fiddler/

            關(guān)于 Web 應(yīng)用的性能優(yōu)化其實(shí)是一個(gè)非常大的話題。本文由于篇幅有限,只能涉及其中的幾個(gè)細(xì)節(jié),并且也無法將這些細(xì)節(jié)的優(yōu)化方式全面的展現(xiàn)給大家。期望本文能夠引起大家對(duì) Web 應(yīng)用尤其是客戶端性能優(yōu)化的充分重視。畢竟服務(wù)端編程技巧已為大家熟知多年,在服務(wù)端挖掘性能的潛力已經(jīng)不大了。而在客戶端的方法改進(jìn)往往能夠得到令人驚奇的性能提升。

               

            posted on 2011-05-23 18:07 風(fēng)舞亂亂 閱讀(411) 評(píng)論(0)  編輯  收藏

            <2025年6月>
            25262728293031
            1234567
            891011121314
            15161718192021
            22232425262728
            293012345

            導(dǎo)航

            公告

            我是一棵無人需要的小草

            隨筆分類(6)

            隨筆檔案(11)

            文章檔案(4)

            相冊(cè)

            風(fēng)舞亂亂

            最新隨筆

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            我是一棵無人需要的小草
            主站蜘蛛池模板: 郸城县| 建昌县| 乌兰浩特市| 泗洪县| 通河县| 广安市| 化德县| 合肥市| 德兴市| 兴山县| 京山县| 碌曲县| 迭部县| 沧州市| 教育| 兴隆县| 本溪| 宝鸡市| 正阳县| 鄂托克前旗| 中方县| 江阴市| 康保县| 元阳县| 阜阳市| 云林县| 韶关市| 富锦市| 北碚区| 康乐县| 腾冲县| 兰西县| 奉节县| 前郭尔| 阳城县| 巴东县| 新晃| 岗巴县| 阿瓦提县| 三江| 楚雄市|