高性能JavaScript筆記二(算法和流程控制、快速響應用戶界面、Ajax)

循環

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

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

優化循環的方法有以下java

一、減小對象成員及數組項的查找次數(使用局部變量保存須要查找的對象成員)web

二、顛倒數組的順序來提升循環性能,也就是從最後一項開始向前處理正則表達式

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

三、相信你們都會盡量的使用for循環而非jQuery的each來遍歷數組,那是由於jQuery的each方法是基於函數的迭代。儘管基於函數的迭代提供了一個更爲便利的迭代方法,但它比基於循環的迭代在慢許多。算法

四、有時候咱們會想究竟是使用if-else呢仍是使用switch,事實上在大多數狀況下switch比if-else運行得要快,因此當判斷多於兩個離散值時,switch語句是更佳的選擇shell

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

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;
                    }
                }
            }

 六、使用遞歸雖然能夠把複雜的算法變得簡單,但遞歸函數若是終止條件不明確或缺乏終止條件會致使函數長時間運行。因此遞歸函數還可能會遇到瀏覽器「調用棧大小限制」瀏覽器

使用優化後的循環來替代長時間運行的遞歸函數能夠提高性能,由於運行一個循環比反覆調用一個函數的開銷要少的多

若是循環資料太多,能夠考慮使用以下介紹的達夫設備原理來提高性能

達夫設備

        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小結

相關文章
相關標籤/搜索