JavaScript性能優化小知識總結(轉)

JavaScript的性能問題不容小覷,這就須要咱們開發人員在編寫JavaScript程序時多注意一些細節,本文很是詳細的介紹了一下JavaScript性能優化方面的知識點,絕對是乾貨。javascript

前言

一直在學習javascript,也有看過《犀利開發Jquery內核詳解與實踐》,對這本書的評價只有兩個字犀利,多是對javascript理解的還不夠透徹異或是本身太笨,更多的是本身不擅於思考懶得思考以致於裏面說的一些精髓都沒有太深刻的理解。css

鑑於想讓本身有一個提高,進不了一個更加廣闊的天地,總得找一個屬於本身的居所好好生存,因此平時會有意無心的去積累一些使用jQuerry的經常使用知識,特別是對於性能要求這一塊,老是會想是否是有更好的方式來實現。html

下面是我總結的一些小技巧,僅供參考。(我先會說一個總標題,而後用一小段話來講明這個意思 再最後用一個demo來簡單言明)java

避免全局查找

在一個函數中會用到全局對象存儲爲局部變量來減小全局查找,由於訪問局部變量的速度要比訪問全局變量的速度更快些node

        function search() {
            //當我要使用當前頁面地址和主機域名
            alert(window.location.href + window.location.host);
        }
        //最好的方式是以下這樣  先用一個簡單變量保存起來
        function search() {
            var location = window.location;
            alert(location.href + location.host);
        }

定時器

若是針對的是不斷運行的代碼,不該該使用setTimeout,而應該是用setInterval,由於setTimeout每一次都會初始化一個定時器,而setInterval只會在開始的時候初始化一個定時器編程

        var timeoutTimes = 0;
        function timeout() {
            timeoutTimes++;
            if (timeoutTimes < 10) {
                setTimeout(timeout, 10);
            }
        }
        timeout();
        //能夠替換爲:
        var intervalTimes = 0;
        function interval() {
            intervalTimes++;
            if (intervalTimes >= 10) {
                clearInterval(interv);
            }
        }
        var interv = setInterval(interval, 10);

字符串鏈接

若是要鏈接多個字符串,應該少使用+=,如數組

s+=a;瀏覽器

s+=b;緩存

s+=c;安全

應該寫成s+=a + b + c;

而若是是收集字符串,好比屢次對同一個字符串進行+=操做的話,最好使用一個緩存,使用JavaScript數組來收集,最後使用join方法鏈接起來

        var buf = [];
        for (var i = 0; i < 100; i++) {
            buf.push(i.toString());
        }
        var all = buf.join("");

避免with語句

和函數相似 ,with語句會建立本身的做用域,所以會增長其中執行的代碼的做用域鏈的長度,因爲額外的做用域鏈的查找,在with語句中執行的代碼確定會比外面執行的代碼要慢,在能不使用with語句的時候儘可能不要使用with語句

 with (a.b.c.d) {
            property1 = 1;
            property2 = 2;
        }
        //能夠替換爲:
        var obj = a.b.c.d;
        obj.property1 = 1;
        obj.property2 = 2;

數字轉換成字符串

般最好用」" + 1來將數字轉換成字符串,雖然看起來比較醜一點,但事實上這個效率是最高的,性能上來講:

(「」 +) > String() > .toString() > new String()

浮點數轉換成整型

不少人喜歡使用parseInt(),其實parseInt()是用於將字符串轉換成數字,而不是浮點數和整型之間的轉換,咱們應該使用Math.floor()或者Math.round()

各類類型轉換

var myVar = "3.14159",
        str = "" + myVar, //  to string  
        i_int = ~ ~myVar,  //  to integer  
        f_float = 1 * myVar,  //  to float  
        b_bool = !!myVar,  /*  to boolean - any string with length 
                                and any number except 0 are true */
        array = [myVar];  //  to array

若是定義了toString()方法來進行類型轉換的話,推薦顯式調用toString(),由於內部的操做在嘗試全部可能性以後,會嘗試對象的toString()方法嘗試可否轉化爲String,因此直接調用這個方法效率會更高

多個類型聲明

在JavaScript中全部變量均可以使用單個var語句來聲明,這樣就是組合在一塊兒的語句,以減小整個腳本的執行時間,就如上面代碼同樣,上面代碼格式也挺規範,讓人一看就明瞭。

插入迭代器

如var name=values[i]; i++;前面兩條語句能夠寫成var name=values[i++]

使用直接量

var aTest = new Array(); //替換爲
        var aTest = [];
        var aTest = new Object; //替換爲
        var aTest = {};
        var reg = new RegExp(); //替換爲
        var reg = /../;
        //若是要建立具備一些特性的通常對象,也可使用字面量,以下:
        var oFruit = new O;
        oFruit.color = "red";
        oFruit.name = "apple";
        //前面的代碼可用對象字面量來改寫成這樣:
        var oFruit = { color: "red", name: "apple" };

使用DocumentFragment優化屢次append

一旦須要更新DOM,請考慮使用文檔碎片來構建DOM結構,而後再將其添加到現存的文檔中。

for (var i = 0; i < 1000; i++) {
            var el = document.createElement('p');
            el.innerHTML = i;
            document.body.appendChild(el);
        }
        //能夠替換爲:
        var frag = document.createDocumentFragment();
        for (var i = 0; i < 1000; i++) {
            var el = document.createElement('p');
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);

使用一次innerHTML賦值代替構建dom元素

對於大的DOM更改,使用innerHTML要比使用標準的DOM方法建立一樣的DOM結構快得多。

        var frag = document.createDocumentFragment();
        for (var i = 0; i < 1000; i++) {
            var el = document.createElement('p');
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);
        //能夠替換爲:
        var html = [];
        for (var i = 0; i < 1000; i++) {
            html.push('<p>' + i + '</p>');
        }
        document.body.innerHTML = html.join('');

經過模板元素clone,替代createElement

不少人喜歡在JavaScript中使用document.write來給頁面生成內容。事實上這樣的效率較低,若是須要直接插入HTML,能夠找一個容器元素,好比指定一個div或者span,並設置他們的innerHTML來將本身的HTML代碼插入到頁面中。一般咱們可能會使用字符串直接寫HTML來建立節點,其實這樣作,1沒法保證代碼的有效性2字符串操做效率低,因此應該是用document.createElement()方法,而若是文檔中存在現成的樣板節點,應該是用cloneNode()方法,由於使用createElement()方法以後,你須要設置屢次元素的屬性,使用cloneNode()則能夠減小屬性的設置次數——一樣若是須要建立不少元素,應該先準備一個樣板節點

        var frag = document.createDocumentFragment();
        for (var i = 0; i < 1000; i++) {
            var el = document.createElement('p');
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);
        //替換爲:
        var frag = document.createDocumentFragment();
        var pEl = document.getElementsByTagName('p')[0];
        for (var i = 0; i < 1000; i++) {
            var el = pEl.cloneNode(false);
            el.innerHTML = i;
            frag.appendChild(el);
        }
        document.body.appendChild(frag);

使用firstChild和nextSibling代替childNodes遍歷dom元素

        var nodes = element.childNodes;
        for (var i = 0, l = nodes.length; i < l; i++) {
            var node = nodes[i];
            //……
        }
        //能夠替換爲:
        var node = element.firstChild;
        while (node) {
            //……
            node = node.nextSibling;

刪除DOM節點

刪除dom節點以前,必定要刪除註冊在該節點上的事件,無論是用observe方式仍是用attachEvent方式註冊的事件,不然將會產生沒法回收的內存。另外,在removeChild和innerHTML=’’兩者之間,儘可能選擇後者. 由於在sIEve(內存泄露監測工具)中監測的結果是用removeChild沒法有效地釋放dom節點

使用事件代理

任何能夠冒泡的事件都不只僅能夠在事件目標上進行處理,目標的任何祖先節點上也能處理,使用這個知識就能夠將事件處理程序附加到更高的地方負責多個目標的事件處理,一樣,對於內容動態增長而且子節點都須要相同的事件處理函數的狀況,能夠把事件註冊提到父節點上,這樣就不須要爲每一個子節點註冊事件監聽了。另外,現有的js庫都採用observe方式來建立事件監聽,其實現上隔離了dom對象和事件處理函數之間的循環引用,因此應該儘可能採用這種方式來建立事件監聽

重複使用的調用結果,事先保存到局部變量

        //避免屢次取值的調用開銷
        var h1 = element1.clientHeight + num1;
        var h2 = element1.clientHeight + num2;
        //能夠替換爲:
        var eleHeight = element1.clientHeight;
        var h1 = eleHeight + num1;
        var h2 = eleHeight + num2;

注意NodeList

最小化訪問NodeList的次數能夠極大的改進腳本的性能

        var images = document.getElementsByTagName('img');
        for (var i = 0, len = images.length; i < len; i++) {

        }

編寫JavaScript的時候必定要知道什麼時候返回NodeList對象,這樣能夠最小化對它們的訪問

  • 進行了對getElementsByTagName()的調用
  • 獲取了元素的childNodes屬性
  • 獲取了元素的attributes屬性
  • 訪問了特殊的集合,如document.forms、document.images等等

要了解了當使用NodeList對象時,合理使用會極大的提高代碼執行速度

優化循環

可使用下面幾種方式來優化循環

  • 減值迭代

大多數循環使用一個從0開始、增長到某個特定值的迭代器,在不少狀況下,從最大值開始,在循環中不斷減值的迭代器更加高效

  • 簡化終止條件

因爲每次循環過程都會計算終止條件,因此必須保證它儘量快,也就是說避免屬性查找或者其它的操做,最好是將循環控制量保存到局部變量中,也就是說對數組或列表對象的遍歷時,提早將length保存到局部變量中,避免在循環的每一步重複取值。

        var list = document.getElementsByTagName('p');
        for (var i = 0; i < list.length; i++) {
            //……
        }

        //替換爲:
        var list = document.getElementsByTagName('p');
        for (var i = 0, l = list.length; i < l; i++) {
            //……
        }
  • 簡化循環體

循環體是執行最多的,因此要確保其被最大限度的優化

  • 使用後測試循環

在JavaScript中,咱們可使用for(;;),while(),for(in)三種循環,事實上,這三種循環中for(in)的效率極差,由於他須要查詢散列鍵,只要能夠,就應該儘可能少用。for(;;)和while循環,while循環的效率要優於for(;;),多是由於for(;;)結構的問題,須要常常跳轉回去。

        var arr = [1, 2, 3, 4, 5, 6, 7];
        var sum = 0;
        for (var i = 0, l = arr.length; i < l; i++) {
            sum += arr[i];
        }

        //能夠考慮替換爲:

        var arr = [1, 2, 3, 4, 5, 6, 7];
        var sum = 0, l = arr.length;
        while (l--) {
            sum += arr[l];
        }

最經常使用的for循環和while循環都是前測試循環,而如do-while這種後測試循環,能夠避免最初終止條件的計算,所以運行更快。

展開循環

當循環次數是肯定的,消除循環並使用屢次函數調用每每會更快。

避免雙重解釋

若是要提升代碼性能,儘量避免出現須要按照JavaScript解釋的字符串,也就是

  • 儘可能少使用eval函數

使用eval至關於在運行時再次調用解釋引擎對內容進行運行,須要消耗大量時間,並且使用Eval帶來的安全性問題也是不容忽視的。

  • 不要使用Function構造器

不要給setTimeout或者setInterval傳遞字符串參數

        var num = 0;
        setTimeout('num++', 10);
        //能夠替換爲:
        var num = 0;
        function addNum() {
            num++;
        }
        setTimeout(addNum, 10);

縮短否認檢測

       if (oTest != '#ff0000') {
            //do something
        }
        if (oTest != null) {
            //do something
        }
        if (oTest != false) {
            //do something
        }
        //雖然這些都正確,但用邏輯非操做符來操做也有一樣的效果:
        if (!oTest) {
            //do something
        }

條件分支

  • 將條件分支,按可能性順序從高到低排列:能夠減小解釋器對條件的探測次數
  • 在同一條件子的多(>2)條件分支時,使用switch優於if:switch分支選擇的效率高於if,在IE下尤其明顯。4分支的測試,IE下switch的執行時間約爲if的一半。
  • 使用三目運算符替代條件分支
        if (a > b) {
            num = a;
        } else {
            num = b;
        }
        //能夠替換爲:
        num = a > b ? a : b;

使用常量

  • 重複值:任何在多處用到的值都應該抽取爲一個常量
  • 用戶界面字符串:任何用於顯示給用戶的字符串,都應該抽取出來以方便國際化
  • URLs:在Web應用中,資源位置很容易變動,因此推薦用一個公共地方存放全部的URL
  • 任意可能會更改的值:每當你用到字面量值的時候,你都要問一下本身這個值在將來是否是會變化,若是答案是「是」,那麼這個值就應該被提取出來做爲一個常量。

避免與null進行比較

因爲JavaScript是弱類型的,因此它不會作任何的自動類型檢查,因此若是看到與null進行比較的代碼,嘗試使用如下技術替換

  • 若是值應爲一個引用類型,使用instanceof操做符檢查其構造函數
  • 若是值應爲一個基本類型,做用typeof檢查其類型
  • 若是是但願對象包含某個特定的方法名,則使用typeof操做符確保指定名字的方法存在於對象上

避免全局量

全局變量應該所有字母大寫,各單詞之間用_下劃線來鏈接。儘量避免全局變量和函數, 儘可能減小全局變量的使用,由於在一個頁面中包含的全部JavaScript都在同一個域中運行。因此若是你的代碼中聲明瞭全局變量或者全局函數的話,後面的代碼中載入的腳本文件中的同名變量和函數會覆蓋掉(overwrite)你的。

//糟糕的全局變量和全局函數
var current = null;
function init(){
//...
}
function change() {
    //...
}
function verify() {
    //...
}
//解決辦法有不少,Christian Heilmann建議的方法是:
//若是變量和函數不須要在「外面」引用,那麼就可使用一個沒有名字的方法將他們全都包起來。
(function(){
var current = null;
function init() {
    //...
}
function change() {
    //...
}
function verify() {
    //...
}
})();
//若是變量和函數須要在「外面」引用,須要把你的變量和函數放在一個「命名空間」中
//咱們這裏用一個function作命名空間而不是一個var,由於在前者中聲明function更簡單,並且能保護隱私數據
myNameSpace = function() {
    var current = null;

    function init() {
        //...
    }

    function change() {
        //...
    }

    function verify() {
        //...
    }

//全部須要在命名空間外調用的函數和屬性都要寫在return裏面
    return {
        init: init,
        //甚至你能夠爲函數和屬性命名一個別名
        set: change
    };
};

尊重對象的全部權

由於JavaScript能夠在任什麼時候候修改任意對象,這樣就能夠以不可預計的方式覆寫默認的行爲,因此若是你不負責維護某個對象,它的對象或者它的方法,那麼你就不要對它進行修改,具體一點就是說:

  • 不要爲實例或原型添加屬性
  • 不要爲實例或者原型添加方法
  • 不要重定義已經存在的方法
  • 不要重複定義其它團隊成員已經實現的方法,永遠不要修改不是由你全部的對象,你能夠經過如下方式爲對象建立新的功能:
  • 建立包含所需功能的新對象,並用它與相關對象進行交互
  • 建立自定義類型,繼承須要進行修改的類型,而後能夠爲自定義類型添加額外功能

循環引用

若是循環引用中包含DOM對象或者ActiveX對象,那麼就會發生內存泄露。內存泄露的後果是在瀏覽器關閉前,即便是刷新頁面,這部份內存不會被瀏覽器釋放。

簡單的循環引用:

        var el = document.getElementById('MyElement');
        var func = function () {
            //…
        }
        el.func = func;
        func.element = el;

可是一般不會出現這種狀況。一般循環引用發生在爲dom元素添加閉包做爲expendo的時候。

        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……
            }
        }
        init();

init在執行的時候,當前上下文咱們叫作context。這個時候,context引用了el,el引用了function,function引用了context。這時候造成了一個循環引用。

下面2種方法能夠解決循環引用:

1)  置空dom對象

       function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……
            }
        }
        init();
        //能夠替換爲:
        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……
            }
            el = null;
        }
        init();

將el置空,context中不包含對dom對象的引用,從而打斷循環應用。

若是咱們須要將dom對象返回,能夠用以下方法:

        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……
            }
            return el;
        }
        init();
        //能夠替換爲:
        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……
            }
            try {
                return el;
            } finally {
                el = null;
            }
        }
        init();

2)  構造新的context

        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = function () {
                //……
            }
        }
        init();
        //能夠替換爲:
        function elClickHandler() {
            //……
        }
        function init() {
            var el = document.getElementById('MyElement');
            el.onclick = elClickHandler;
        }
        init();

把function抽到新的context中,這樣,function的context就不包含對el的引用,從而打斷循環引用。

經過javascript建立的dom對象,必須append到頁面中

IE下,腳本建立的dom對象,若是沒有append到頁面中,刷新頁面,這部份內存是不會回收的!

        function create() {
            var gc = document.getElementById('GC');
            for (var i = 0; i < 5000; i++) {
                var el = document.createElement('div');
                el.innerHTML = "test";
                //下面這句能夠註釋掉,看看瀏覽器在任務管理器中,點擊按鈕而後刷新後的內存變化
                gc.appendChild(el);
            }
        }

釋放dom元素佔用的內存

將dom元素的innerHTML設置爲空字符串,能夠釋放其子元素佔用的內存。

在rich應用中,用戶也許會在一個頁面上停留很長時間,可使用該方法釋放積累得愈來愈多的dom元素使用的內存。

釋放javascript對象

在rich應用中,隨着實例化對象數量的增長,內存消耗會愈來愈大。因此應當及時釋放對對象的引用,讓GC可以回收這些內存控件。

對象:obj = null

對象屬性:delete obj.myproperty

數組item:使用數組的splice方法釋放數組中不用的item

避免string的隱式裝箱

對string的方法調用,好比’xxx’.length,瀏覽器會進行一個隱式的裝箱操做,將字符串先轉換成一個String對象。推薦對聲明有可能使用String實例方法的字符串時,採用以下寫法:

var myString = new String(‘Hello World’);

鬆散耦合

一、解耦HTML/JavaScript

JavaScript和HTML的緊密耦合:直接寫在HTML中的JavaScript、使用包含內聯代碼的<script>元素、使用HTML屬性來分配事件處理程序等

HTML和JavaScript的緊密耦合:JavaScript中包含HTML,而後使用innerHTML來插入一段html文本到頁面

其實應該是保持層次的分離,這樣能夠很容易的肯定錯誤的來源,因此咱們應確保HTML呈現應該儘量與JavaScript保持分離

二、解耦CSS/JavaScript

顯示問題的惟一來源應該是CSS,行爲問題的惟一來源應該是JavaScript,層次之間保持鬆散耦合纔可讓你的應用程序更加易於維護,因此像如下的代碼element.style.color=」red」儘可能改成element.className=」edit」,並且不要在css中經過表達式嵌入JavaScript

三、解耦應用程序/事件處理程序

將應用邏輯和事件處理程序相分離:一個事件處理程序應該從事件對象中提取,並將這些信息傳送給處理應用邏輯的某個方法中。這樣作的好處首先可讓你更容易更改觸發特定過程的事件,其次能夠在不附加事件的狀況下測試代碼,使其更易建立單元測試

性能方面的注意事項

一、儘可能使用原生方法

二、switch語句相對if較快

經過將case語句按照最可能到最不可能的順序進行組織

三、位運算較快

當進行數字運算時,位運算操做要比任何布爾運算或者算數運算快

四、巧用||&&布爾運算符

        function eventHandler(e) {
            if (!e) e = window.event;
        }
        //能夠替換爲:
        function eventHandler(e) {
            e = e || window.event;
        }
        if (myobj) {
            doSomething(myobj);
        }
        //能夠替換爲:
        myobj && doSomething(myobj);

避免錯誤應注意的地方

一、每條語句末尾須加分號

在if語句中,即便條件表達式只有一條語句也要用{}把它括起來,以避免後續若是添加了語句以後形成邏輯錯誤

二、使用+號時需謹慎

JavaScript 和其餘編程語言不一樣的是,在 JavaScript 中,’+'除了表示數字值相加,字符串相鏈接之外,還能夠做一元運算符用,把字符串轉換爲數字。於是若是使用不當,則可能與自增符’++’混淆而引發計算錯誤

        var valueA = 20;
        var valueB = "10";
        alert(valueA + valueB);     //ouput: 2010 
        alert(valueA + (+valueB));  //output: 30 
        alert(valueA + +valueB);    //output:30 
        alert(valueA ++ valueB);     //Compile error

三、使用return語句須要注意

一條有返回值的return語句不要用()括號來括住返回值,若是返回表達式,則表達式應與return關鍵字在同一行,以免壓縮時,壓縮工具自動加分號而形成返回與開發人員不一致的結果

        function F1() {
            var valueA = 1;
            var valueB = 2;
            return valueA + valueB;
        }
        function F2() {
            var valueA = 1;
            var valueB = 2;
            return
            valueA + valueB;
        }
        alert(F1());  //output: 3 
        alert(F2());  //ouput: undefined

==和===的區別

避免在if和while語句的條件部分進行賦值,如if (a = b),應該寫成if (a == b),可是在比較是否相等的狀況下,最好使用全等運行符,也就是使用===和!==操做符會相對於==和!=會好點。==和!=操做符會進行類型強制轉換

        var valueA = "1";
        var valueB = 1;
        if (valueA == valueB) {
            alert("Equal");
        }
        else {
            alert("Not equal");
        }
        //output: "Equal"
        if (valueA === valueB) {
            alert("Equal");
        }
        else {
            alert("Not equal");
        }
        //output: "Not equal"

不要使用生偏語法

不要使用生偏語法,寫讓人迷惑的代碼,雖然計算機可以正確識別並運行,可是晦澀難懂的代碼不方便之後維護

函數返回統一類型

雖然JavaScript是弱類型的,對於函數來講,前面返回整數型數據,後面返回布爾值在編譯和運行均可以正常經過,但爲了規範和之後維護時容易理解,應保證函數應返回統一的數據類型

老是檢查數據類型

要檢查你的方法輸入的全部數據,一方面是爲了安全性,另外一方面也是爲了可用性。用戶隨時隨地都會輸入錯誤的數據。這不是由於他們蠢,而是由於他們很忙,而且思考的方式跟你不一樣。用typeof方法來檢測你的function接受的輸入是否合法

什麼時候用單引號,什麼時候用雙引號

雖然在JavaScript當中,雙引號和單引號均可以表示字符串, 爲了不混亂,咱們建議在HTML中使用雙引號,在JavaScript中使用單引號,但爲了兼容各個瀏覽器,也爲了解析時不會出錯,定義JSON對象時,最好使用雙引號

部署

  • 用JSLint運行JavaScript驗證器來確保沒有語法錯誤或者是代碼沒有潛在的問
  • 部署以前推薦使用壓縮工具將JS文件壓縮
  • 文件編碼統一用UTF-8
  • JavaScript 程序應該儘可能放在 .js 的文件中,須要調用的時候在 HTML 中以 <script src=」filename.js」> 的形式包含進來。JavaScript 代碼若不是該 HTML 文件所專用的,則應儘可能避免在 HTML 文件中直接編寫 JavaScript 代碼。由於這樣會大大增長 HTML 文件的大小,無益於代碼的壓縮和緩存的使用。另外,<script src=」filename.js」> 標籤應儘可能放在文件的後面,最好是放在</body>標籤前。這樣會下降因加載 JavaScript 代碼而影響頁面中其它組件的加載時間。

永遠不要忽略代碼優化工做,重構是一項從項目開始到結束須要持續的工做,只有不斷的優化代碼才能讓代碼的執行效率愈來愈好。

 

來源: <http://www.codeceo.com/article/javascript-performance-tips.html>

相關文章
相關標籤/搜索