如何優化你的JS代碼

JS代碼的執行效率往往直接影響了頁面的性能,有的時候,實現同樣的功能,不同的JS代碼往往在效率上相差很多,有的時候僅僅是由於我們的書寫習慣導致的,當然在高級點的瀏覽器中,它們大多都已經幫我們優化了,但是在中國,萬惡的IE6仍然大量的存在,我們不得不去考慮它。對於JS代碼的優化,實際上有很多的情況,有些影響是比較小的,而有些是比較嚴重的,本文中,我把幾個我認爲影響比較嚴重的情況列出來,供大家參考。

1、字符串的拼接

字符串的拼接在我們開發中會經常遇到,所以我把其放在首位,我們往往習慣的直接用+=的方式來拼接字符串,其實這種拼接的方式效率非常的低,我們可以用一種巧妙的方法來實現字符串的拼接,那就是利用數組的join方法。

<div class="one" id="one"></div>   
<input type="button" value="效率低" οnclick="func1()" />   
<input type="button" value="效率高" οnclick="func2()" /> 
//效率低的   
function func1(){   
    var start = new Date().getTime();   
    var template = "";   
    for(var i = 0; i < 10000; i++){   
        template += "<input type='button' value='a'>";   
    }   
    var end = new Date().getTime();   
    document.getElementById("one").innerHTML = template;   
    alert("用時:" + (end - start) + "毫秒");   
}   
//效率高的   
function func2(){   
    var start = new Date().getTime();   
    var array = [];   
    for(var i = 0; i < 10000; i++){   
        array[i] = "<input type='button' value='a'>";   
    }   
    var end = new Date().getTime();   
    document.getElementById("one").innerHTML = array.join("");   
    alert("用時:" + (end - start) + "毫秒");   
}   

我們看看其在不同瀏覽器下執行的情況
這裏寫圖片描述

我們會發現,在IE6下其差別是相當明顯的,其實這種情況在IE的高版本中體現的也非常明顯,但是在Firefox下卻沒有多大的區別,相反第二種的相對效率還要低點,不過只是差別2ms左右,而Chrome也和Firefox類似。另外在這裏順便說明一下,在我們給數組添加元素的時候,很多人喜歡用數組的原生的方法push,其實直接用arr[i]或者arr[arr.length]的方式要快一點,大概在10000次循環的情況IE瀏覽器下會有十幾毫秒的差別。

2、for循環

for循環是我們經常會遇到的情況,我們先看看下面例子:

<input type="button" value="效率低" οnclick="func1()" />   
<input type="button" value="效率高" οnclick="func2()" /> 
var arr = [];   
for(var i = 0; i < 10000; i++){   
    arr[i] = "<div>" + i + "</div>";   
}   
document.body.innerHTML += arr.join("");   

//效率低的   
function func1(){   
    var divs = document.getElementsByTagName("div");   
    var start = new Date().getTime();   
    for(var i = 0; i < divs.length; i++){   
        //"效率低"   
    }   
    var end = new Date().getTime();   
    alert("用時:" + (end - start) + "毫秒");   
}   
//效率高的   
function func2(){   
    var divs = document.getElementsByTagName("div");   
    var start = new Date().getTime();   
    for(var i = 0, len = divs.length; i < len; i++){   
        //"效率高"   
    }   
    var end = new Date().getTime();   
    alert("用時:" + (end - start) + "毫秒");   
} 

這裏寫圖片描述

由上表可以看出,在IE6.0下,其差別是非常明顯,而在Firefox和Chrome下幾乎沒有差別,之所以在IE6.0下會有這種情況,主要是因爲for循環在執行中,第一種情況會每次都計算一下長度,而第二種情況卻是在開始的時候計算長度,並把其保存到一個變量中,所以其執行效率要高點,所以在我們使用for循環的時候,特別是需要計算長度的情況,我們應該開始將其保存到一個變量中。但是並不是只要是取長度都會出現如此明顯的差別,如果我們僅僅是操作一個數組,取得的是一個數組的長度,那麼其實兩種方式的寫法都差不多,我們看下面的例子:

<input type="button" value="效率低" οnclick="func1()" />   
<input type="button" value="效率高" οnclick="func2()" />  
var arr2 = [];   
for(var i = 0; i < 10000; i++){   
    arr2[i] = "<div>" + i + "</div>";   
}   
//效率低的   
function func1(){   
    var start = new Date().getTime();   
    for(var i = 0; i < arr2.length; i++){   
        //"效率低"   
    }   
    var end = new Date().getTime();   
    alert("用時:" + (end - start) + "毫秒");   
}   
//效率高的   
function func2(){   
    var start = new Date().getTime();   
    for(var i = 0, len = arr2.length; i < len; i++){   
        //"效率高"   
    }   
    var end = new Date().getTime();   
    alert("用時:" + (end - start) + "毫秒");   
} 

這裏寫圖片描述

從上表可以看出,如果僅僅是一個數組的話,我們看到其實兩種寫法都是差不多的,其實如果我們把循環再上調到100000次的話,也僅僅是差別幾毫秒而已,所以在數組的情況下,我認爲都是一樣的。對於for循環的優化,也有人提出很多點,有人認爲用-=1,或者從大到小的方式循環等等,我認爲是完全沒有必要的,這些優化往往實際情況下根本沒有表現出來,換句話說只是計算機級別的微小的變化,但是給我們帶來的卻是代碼的可讀性大大的降低,所以實在是得不償失。

3、減少頁面的重繪

減少頁面重繪雖然本質不是JS本身的優化,但是其往往是由JS引起的,而重繪的情況往往是嚴重影響頁面性能的,所以完全有必要拿出來,我們看下面例子:

<div id="demo"></div>   
<input type="button" value="效率低" οnclick="func1()" />   
<input type="button" value="效率高" οnclick="func2()" /> 
var str = "<div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div><div>這是一個測試字符串</div>";   
//效率低的   
function func1(){   
    var obj = document.getElementById("demo");   
    var start = new Date().getTime();   
    for(var i = 0; i < 100; i++){   
        obj.innerHTML += str + i;   
    }   
    var end = new Date().getTime();   
    alert("用時 " + (end - start) + " 毫秒");   
}   
//效率高的   
function func2(){   
    var obj = document.getElementById("demo");   
    var start = new Date().getTime();   
    var arr = [];   
    for(var i = 0; i < 100; i++){   
        arr[i] = str + i;   
    }   
    obj.innerHTML = arr.join("");   
    var end = new Date().getTime();   
    alert("用時 " + (end - start) + " 毫秒");   

這裏寫圖片描述

可以看到的是,這簡直是一個驚人的結果,僅僅100次的循環,不管是在什麼瀏覽器下,都出現瞭如此之大的差別,另外我們還發現,在這裏,IE6的執行效率居然比起Firefox還要好很多,可見Firefox在頁面重繪這方面並沒有做一些的優化。這裏還要注意的是,一般影響頁面重繪的不僅僅是innerHTML,如果改變元素的樣式,位置等情況都會觸發頁面重繪,所以在平時一定要注意這點。

4、減少作用域鏈上的查找次數
我們知道,js代碼在執行的時候,如果需要訪問一個變量或者一個函數的時候,它需要遍歷當前執行環境的作用域鏈,而遍歷是從這個作用域鏈的前端一級一級的向後遍歷,直到全局執行環境,所以這裏往往會出現一個情況,那就是如果我們需要經常訪問全局環境的變量對象的時候,我們每次都必須在當前作用域鏈上一級一級的遍歷,這顯然是比較耗時的,我們看下面的例子:

<div id="demo"></div>   
<input id="but1" type="button" οnclick="func1()" value="效率低"/>   
<input id="but2" type="button" οnclick="func2()" value="效率高"/>   

function func1(){   
    var start = new Date().getTime();   
    for(var i = 0; i < 10000; i++){   
        var but1 = document.getElementById("but1");   
        var but2 = document.getElementById("but2");   
        var inputs = document.getElementsByTagName("input");   
        var divs = document.getElementsByTagName("div");   
        var but1 = document.getElementById("but1");   
        var but2 = document.getElementById("but2");   
        var inputs = document.getElementsByTagName("input");   
        var divs = document.getElementsByTagName("div");   
        var but1 = document.getElementById("but1");   
        var but2 = document.getElementById("but2");   
        var inputs = document.getElementsByTagName("input");   
        var divs = document.getElementsByTagName("div");   
        var but1 = document.getElementById("but1");   
        var but2 = document.getElementById("but2");   
        var inputs = document.getElementsByTagName("input");   
        var divs = document.getElementsByTagName("div");   
        var but1 = document.getElementById("but1");   
        var but2 = document.getElementById("but2");   
        var inputs = document.getElementsByTagName("input");   
        var divs = document.getElementsByTagName("div");   
        var but1 = document.getElementById("but1");   
        var but2 = document.getElementById("but2");   
        var inputs = document.getElementsByTagName("input");   
        var divs = document.getElementsByTagName("div");   
    }   
    var end = new Date().getTime();   
    alert("用時 " + (end - start) + " 毫秒");   
}   
function func2(){   
    var start = new Date().getTime();   
    var doc = document;   
    for(var i = 0; i < 10000; i++){   
        var but1 = doc.getElementById("but1");   
        var but2 = doc.getElementById("but2");   
        var inputs = doc.getElementsByTagName("input");   
        var divs = doc.getElementsByTagName("div");   
        var but1 = doc.getElementById("but1");   
        var but2 = doc.getElementById("but2");   
        var inputs = doc.getElementsByTagName("input");   
        var divs = doc.getElementsByTagName("div");   
        var but1 = doc.getElementById("but1");   
        var but2 = doc.getElementById("but2");   
        var inputs = doc.getElementsByTagName("input");   
        var divs = doc.getElementsByTagName("div");   
        var but1 = doc.getElementById("but1");   
        var but2 = doc.getElementById("but2");   
        var inputs = doc.getElementsByTagName("input");   
        var divs = doc.getElementsByTagName("div");   
        var but1 = doc.getElementById("but1");   
        var but2 = doc.getElementById("but2");   
        var inputs = doc.getElementsByTagName("input");   
        var divs = doc.getElementsByTagName("div");   
        var but1 = doc.getElementById("but1");   
        var but2 = doc.getElementById("but2");   
        var inputs = doc.getElementsByTagName("input");   
        var divs = doc.getElementsByTagName("div");   
    }   
    var end = new Date().getTime();   
    alert("用時 " + (end - start) + " 毫秒");   

上面代碼中,第二種情況是先把全局對象的變量放到函數裏面先保存下來,然後直接訪問這個變量,而第一種情況是每次都遍歷作用域鏈,直到全局環境,我們看到第二種情況實際上只遍歷了一次,而第一種情況卻是每次都遍歷了,所以我們看看其執行結果:
這裏寫圖片描述

從上表中可以看出,其在IE6下差別還是非常明顯的,而且這種差別在多級作用域鏈和多個全局變量的情況下還會表現的非常明顯。

5、避免雙重解釋

雙重解釋的情況也是我們經常會碰到的,有的時候我們沒怎麼考慮到這種情況會影響到效率,雙重解釋一般在我們使用eval、new Function和setTimeout等情況下會遇到,我們看看下面的例子:

<div id="demo"></div>   
<input id="but1" type="button" οnclick="func1()" value="效率低"/>   
<input id="but2" type="button" οnclick="func2()" value="效率高"/>   
var sum, num1 = 1, num2 = 2;   
function func1(){   
    var start = new Date().getTime();   
    for(var i = 0; i < 10000; i++){   
        var func = new Function("sum+=num1;num1+=num2;num2++;");   
        func();   
    }   
    var end = new Date().getTime();   
    alert("用時 " + (end - start) + " 毫秒");   
}   

function func2(){   
    var start = new Date().getTime();   
    for(var i = 0; i < 10000; i++){   
        sum+=num1;   
        num1+=num2;   
        num2++;   
    }   
    var end = new Date().getTime();   
    alert("用時 " + (end - start) + " 毫秒");   
} 

第一種情況我們是使用了new Function來進行雙重解釋,而第二種是避免了雙重解釋,我們看看在不同瀏覽器下的表現:
這裏寫圖片描述
可以看到,在所有的瀏覽器中,雙重解釋都是有很大開銷的,所以在實際當中要儘量避免雙重解釋。

感謝”SeaSunK”對第四點測試報告錯誤的指正,現在已經修改過來了。至於最後一點提出的func1每次都初始化,沒有可比性,所以我給換了eval,結果發現,在IE6.0下還是有影響,而且在Firefox下,使用eval對效率的影響程度更加厲害,在Firefox下,如果10000次循環,需要十多秒的時間,所以我把循環都變成了1000次。看代碼和報告。

var sum, num1 = 1, num2 = 2;   
function func1(){   
    var start = new Date().getTime();   
    for(var i = 0; i < 1000; i++){   
        eval("sum+=num1;num1+=num2;num2++;");   
    }   
    var end = new Date().getTime();   
    alert("用時 " + (end - start) + " 毫秒");   
}   
function func2(){   
    var start = new Date().getTime();   
    for(var i = 0; i < 1000; i++){   
        sum+=num1;   
        num1+=num2;   
        num2++;   
    }   
    var end = new Date().getTime();   
    alert("用時 " + (end - start) + " 毫秒"); 

這裏寫圖片描述

發佈了23 篇原創文章 · 獲贊 52 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章