close
文章出處

循環

在javaScript中的四種循環中(for、for-in、while、do-while),只有for-in循環比其它幾種明顯要慢,另外三種速度區別不大

有一點需要注意的是,javascript沒有塊級作用域,只有函數級作用域,也就是說在for循環初始化中的var語句會創建一個函數級變量而非循環級變量

優化循環的方法有如下

1、減少對象成員及數組項的查找次數(使用局部變量保存需要查找的對象成員)

2、顛倒數組的順序來提高循環性能,也就是從最后一項開始向前處理

        for (var i = arr.length-1; i >= 0 ; i--) {
            //process
        }

3、相信大家都會盡可能的使用for循環而非jQuery的each來遍歷數組,那是因為jQuery的each方法是基于函數的迭代。盡管基于函數的迭代提供了一個更為便利的迭代方法,但它比基于循環的迭代在慢許多。

4、有時候我們會想到底是使用if-else呢還是使用switch,事實上在大多數情況下switch比if-else運行得要快,所以當判斷多于兩個離散值時,switch語句是更佳的選擇

5、優化if-else最簡單的方法就是確保最可能出現的條件放在首位,另外一個方法就是優化條件判斷的次數,看下面的代碼您就懂了

if (value == 0) {
                return result0;
            } else if (value == 1) {
                return result1;
            } else if (value == 2) {
                return result2;
            } else if (value == 3) {
                return result3;
            } else if (value == 4) {
                return result4;
            } else if (value == 5) {
                return result5;
            } else if (value == 6) {
                return result6;
            } else if (value == 7) {
                return result7;
            } else if (value == 8) {
                return result8;
            } else if (value == 9) {
                return result9;
            } else if (value == 10) {
                return result10;
            }

下面這種方法就是使用二分搜索法將值域分成一系列區間,然后逐步縮小區范圍,對上面的例子進行的優化

            if (value < 6) {
                if (value < 3) {
                    if (value == 0) {
                        return result0;
                    } else if (value == 1) {
                        return result1;
                    } else {
                        return result2;
                    }
                } else {
                    if (value == 3) {
                        return result3;
                    } else if (value == 4) {
                        return result4;
                    } else {
                        return result5;
                    }
                }
            } else {
                if (value < 8) {
                    if (value == 6) {
                        return result06;
                    } else if (value == 7) {
                        return result7;
                    }
                } else {
                    if (value == 8) {
                        return result8;
                    } else if (value == 9) {
                        return result9;
                    } else {
                        return result10;
                    }
                }
            }

 6、使用遞歸雖然可以把復雜的算法變得簡單,但遞歸函數如果終止條件不明確或缺少終止條件會導致函數長時間運行。所以遞歸函數還可能會遇到瀏覽器“調用棧大小限制”

使用優化后的循環來替代長時間運行的遞歸函數可以提升性能,因為運行一個循環比反復調用一個函數的開銷要少的多

如果循環資料太多,可以考慮使用如下介紹的達夫設備原理來提升性能

達夫設備

        var iterations = Math.floor(items.length / 8),
            startAt = items.length % 8,
            i = 0;
        do {
            //每次循環最多可調用8次process
            switch (startAt) {
                case 0: process(items[i++]);
                case 7: process(items[i++]);
                case 6: process(items[i++]);
                case 5: process(items[i++]);
                case 4: process(items[i++]);
                case 3: process(items[i++]);
                case 2: process(items[i++]);
                case 1: process(items[i++]);
            }
            startAt = 0;
        } while (--iterations);
        var i = items.length % 8;
        while (i) {
            process(items[i--]);
        }
        i = Math.floor(items.length / 8);
        while (i) {
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);
            process(items[i--]);            
        }

Memoization

避免重復是Memoization的核心思想,它緩存前一次計算結果供后續使用,下面代碼就是利用緩存結果的思想計算階乘的

        function memFactorial(n) {
            if (!memFactorial.cache) {
                memFactorial.cache = {
                    "0": 1,
                    "1": 1
                };
            }
            if (!memFactorial.cache.hasOwnProperty(n)) {
                memFactorial.cache[n] = n * memFactorial(n - 1);
            }
            return memFactorial.cache[n];
        }

寫成通用方法如下代碼所示:

        function memoize(fundamental, cache) {
            cache = cache || {};
            var shell = function (arg) {
                if (!cache.hasOwnProperty(arg)) {
                    cache[arg] = fundamental(arg);
                }
                return cache[arg];
            }
            return shell;
        }
    //下面是調用示例
        function factorial(n) {
            if (n==0) {
                return 1;
            }else{
                return n*factorial(n-1);
            }
        }
        var memfactorial = memoize(factorial, { "0": 1, "1": 1 });
        memfactorial(6);

 

算法和流程控制小結

字符串優化

        str += "one" + "two";
        //以下代碼分別用兩行語句直接附加內容給str,從而避免產生臨時字符串 性能比上面提升10%到40%;
        str += "one";
        str += "two";
        //同樣你可以用如下一句達到上面同樣的性能提升
        str = str + "one" + "two";
        //事實上 str = str + "one" + "two";等價于 str = ((str + "one") + "two");

或許大家都喜歡用Array.prototype.join方法將數組中所有元素合并成一個字符串,雖然它是在IE7及更早版本瀏覽器 中合并大量字符串唯一高效的途徑,但是事實上在現代大多數瀏覽器中,數組項連接比其它字符串連接的方法更慢。

在大多數情況下,使用concat比使用簡單的+和+=要稍慢些。

快速響應用戶界面

javascript是單線程的,共用于執行javascript和更新用戶界面的進程通常被稱作為“瀏覽器UI線程”,也就是說某一時間,UI線程只能做一件事情,要么執行javascript,要么更新用戶界面,假設你當前的javascript需要執行很長時間,而這時候用戶點擊了界面按鈕,那么UI線程則無法立即響應用戶點擊更新按鈕UI狀態,導致用戶以為沒點擊而進行多次點擊操作

所以有些瀏覽器會限制javascript任務的運行時間

單個javascript操作花費的總時間不應該超過100毫秒,所以應該限制所有javascript任務在100毫秒或更短的時間內完成,但是。。。

應該讓出UI控制權(也就是停止執行javascript)使得UI線程有機會更新,然后再繼續執行javascript

使用setTimeout和setInterval來創建定時器(定時器代碼只有在創建它的函數執行完成之后,才有可能被執行)

使用setTimeout和使用setInterval幾乎相同,唯一的區別在于如果UI隊列中已經存在由同一個setInterval創建的任務,那么后續任務不會被添加到UI隊列中

每個定時器最好使用至少25毫秒,因為更小的延時對于大多數UI更新根本不夠用

        function processArray(items,process,callback) {
            var todo = items.concat();//克隆原數組
            setTimeout(function () {
                process(todo.shift());
                if (todo.length > 0) {
                    setTimeout(arguments.callee, 25);
                } else {
                    callback(items);
                }
            }, 25);
        }
        //for example
        var items = [123, 45, 443, 35, 53, 7544, 7654, 75, 75, 32, 653, 76];
        function outputValue(value) {
            console.log(value);
        }
        processArray(items, outputValue, function () {
            console.log("Done");
        });

同理,分割任務也是一樣。

        function multiStep(steps,args,callback) {
            var tasks = steps.concat();//克隆數組
            setTimeout(function () {
                var task = tasks.shift();//執行下一個任務
                task.apply(null, args || []);
                //檢查是否還有其它任務
                if (tasks.length > 0) {
                    setTimeout(arguments.callee, 25);
                } else {
                    callback();
                }
            }, 25);
        }
        //for example
        var tasks = [openDoc, writeText, closeDoc, updateUI];//由待執行函數組成的數組
        multiStep(tasks, [id], function () {
            console.log("Done");
        });

 相信了解過Html5的都知道,h5中引用了web worker來執行與ui更新無關的長腳本,這個也可以改善用戶響應時間。具體請見我的另外博文

web worker

快速響應的用戶界面小結

數據傳輸

有5種常用技術用于向服務器請求數據:

  1. XMLHttpRquest(XHR)
  2. Dynamic script tag insertion動態腳本注入
  3. iframes
  4. Commet
  5. Multipart XHR

XMLHttpRquest:允許異步發送和接收數據,可以在請求中添加任何頭信息和參數,并讀取服務器返回的所有頭信息及響應文本

使用XHR時,POST和GET的對比,對于那些不會改變服務器狀態,只會獲取數據(這種稱作冪等行為)的請求,應該使用GET,經GET請求的數據會被緩存起來,如果需要多次請求同一數據的時候它會有助于提升性能 。

只有當請求的URL加上參數的長度接近或超過2048個字符時,才應該用POST獲取數據,這是因為IE限制URL長度,過長時將會導致請求的URL截斷

另外需要注意的是:因為響應消息作為腳本標簽的源碼,所以返回的數據必須是可執行的javascript代碼,所以你不能使用純xml,純json或其它任何格式的數據,無論哪種格式,都必須封裝在一個回調函數中

使用XHR發送數據到服務器時,GET方式會更快,因為對于少量數據而言,一個GET請求往服務器只發送一個數據包,而一個POST請求至少發送兩個數據包,一個裝載頭信息,另一個裝載POST正文,POST更適合發送大量數據到服務器

Multipart XHR:允許客戶端只用一個HTTP請求就可以從服務器向客戶羰傳送多個資源,它通過在服務器端將資源打包成一個由雙方約定的字符串分割的長字符串并發送到客戶端,然后用javaScript處理那個長字符串,并根據mime-type類型和傳入的其它頭信息解析出每個資源

multipart XHR使用了流的功能,通過監聽readyState為3的狀態,我們可以在一個較大的響應還沒有完全接受之前就把它分段處理,這樣我們就可以實時處理響應片段,這也是MXHR能大幅提升性能的主要原因

使用Multipart XHR的缺點(但是它能顯著提升頁面的整體性能):

  1. 獲得的資源不能被瀏覽器緩存
  2. 老版本的IE不支持readyState為3的狀態和data:URL(圖片不是由base64字符串轉換成二進制,而是使用data:URL的方式創建,并指定mime-type為image/jpeg    使用readyState為3是因為你不可能等所有數據都傳輸完成再處理,那樣會很慢)

 

Beacons技術

使用javascript創建一個新的Image對象,并把src屬性設置為服務器上腳本的URL,該URL包含我們要通過GET傳回的鍵值對數據(并沒有創建img元素,也沒有插入DOM),服務器會接收到數據并保存起來,它需向客戶端發送任何回饋信息。這種方式是給服務器回傳信息最有效的方式,雖然它的優點是性能消耗很小,但它的缺點也顯而易見

發送的數據長度限制得相當小

如果要接收服務器端返回的數據一種方式是監聽Image對象的load事件,另外一種方式就是檢查服務器返回圖片的寬高來判斷服務器狀態

數據格式

現在xml這種數據格式已全然被json取代了,原因很多,主要原因是XML文件大小太大,解析速度慢,雖然XPath在解析xml文檔時比getElementsByTagName快許多,但XPath并未得到廣泛支持

JSON相對xml來說,文件體積相對更少,通用性強

JSON數據被當成另一個JavaScript文件并作為原生代碼執行,為實現這一點,這些數據必須封裝在一個回調函數中,這就是所謂的JSON填充(JSON with padding)JSON-P

最快的JSON格式就是使用數組形式的JSON-P

使用JSON-P必須注意安全性,因為JSON-P必須是可執行的JavaScript,它可能被任何人調用并使用動態腳本注入技術插入到網站,另一方面,JSON在eval前是無效的JavaScript,使用XHR時它只是被當作字符串獲取,所以不要把任何敏感數據編碼在JSON-P中。

理想的數據格式應該是只包含必要的結構,以便你可以分解出每一個獨立的字段,所以自定義格式相對來說體積更小點,可以快速下載,且易于解析(只要用split函數即可),所以當你創建自定義格式時,最重要的決定之一就是采用哪種分隔符

        var rows = req.responseText.split(/\u0001/);//正則表達式作為分隔符
        var rows = req.responseText.split("\u0001");//字符串作為分隔符(更為保險)

 

數據格式總結

緩存數據

  • 在服務器,設置HTTP頭信息以確保你的響應會被瀏覽器緩存
  • 在客戶端,把獲取到的信息存儲到本地,從而避免再次請求

如果你希望Ajax響應能被瀏覽器緩存,請必須使用GET方式發出請求。設置Expires頭信息是確保瀏覽器緩存Ajax響應最簡單的方法,而且其緩存內容能跨頁面和跨會話

當然也可以手工管理本地緩存,也就是直接把服務器接收到的數據緩存起來

用習慣了Ajax類庫了,然后卻連自己怎么寫一個XMLHttpRequest都不知道了,事實上很多Ajax類庫都有這樣那樣的局限(比如說不允許你直接訪問readystatechange事件,這也意味著你必須等待完整的響應接收完畢之后才能開始使用它)所以......

Ajax小結


不含病毒。www.avast.com
arrow
arrow
    全站熱搜

    AutoPoster 發表在 痞客邦 留言(0) 人氣()