Web前端性能優化——編寫高效的JavaScript

前言css

隨着計算機的發展,Web富應用時代的到來,Web 2.0早已再也不是用div+css高質量還原設計的時代。自Gmail網頁版郵件服務的問世開始,Web前端開發也開啓了新的紀元。用戶需求不斷提升,各類新的技術層出不窮,前端工程師的地位也愈來愈重要。然而任何事物都是有兩面性的,隨着前端技術的發展,前端業務愈來愈繁重,這大大增長了JS代碼量。所以,要提升Web的性能,咱們不只須要關注頁面加載的時間,還要注重在頁面上操做的響應速度。那麼,接下來咱們討論幾種可以提升JavaScript效率的方法。前端

 

1、從JavaScript的做用域談起node

當JavaScript代碼執行時,JavaScript引擎會建立一個執行環境,又叫執行上下文。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們的行爲,每一個執行環境都有一個與它關聯的變量對象,環境中定義的全部函數、變量都保存在這個對象中。在頁面加載的時候,JavaScript引擎會建立一個全局的執行環境,全部全局變量和函數都是做爲window對象(瀏覽器中)的屬性和方法建立的。在此以後,每執行一個函數,JavaScript引擎都會建立一個對應的執行環境,並將該環境放入環境棧中,因此當前正在執行的函數的執行環境是在環境棧的最頂部的,當函數執行完畢以後,其執行環境會彈出棧,並被銷燬,保存在其中的變量和函數定義也會被銷燬。jquery

當代碼在一個執行環境中執行時,JavaScript引擎會建立變量對象的一個做用域鏈,它能夠保證對執行環境有權訪問的變量和函數的有序訪問。做用域鏈的前端始終是當前執行的代碼所在的環境的變量對象。全局環境的做用域鏈中只有一個變量對象,它定義了全部可用的全局變量和函數。當函數被建立時,JavaScript引擎會把建立時執行環境的做用域鏈賦給函數的內部屬性[[scope]];當函數被執行時,JavaScript引擎會建立一個活動對象,最開始時這個活動對象只有一個變量,即arguments對象。該活動對象會出如今執行環境做用域鏈的頂端,接下來是函數[[scope]]屬性中的對象。數組

當須要查找某個變量或函數時,JavaScript引擎會經過執行環境的做用域鏈來查找變量和函數,從做用域鏈的頂端開始,若是沒找到,則向下尋找直至找到爲止。若一直到全局做用域都沒有找到,則該變量或函數爲undefined。瀏覽器

舉個栗子:前端工程師

function add(a,b) {
    return a + b;
}

var result = add(2,3);

代碼執行時,add函數有一個僅包含全局變量對象的[[scope]]屬性,add函數執行時,JavaScript引擎建立新的執行環境以及一個包含this、arguments、a、b的活動對象,並將其添加到做用域鏈中。以下圖所示:閉包

 

 

2、使用局部變量dom

瞭解了做用域鏈的概念,咱們應該知道在查找變量會從做用域鏈的頂端開始一層一層的向下找。顯然,查找的層數越多,花費的時間越多。因此爲了提升查找的速度,咱們應該儘可能使用 局部變量(到目前爲止,局部變量是JavaScript中讀寫最快的標識符)。函數

例如:

function createEle() {
    document.createElement("div");
}
function createEle() {
    var doc = document;
    doc.createElement("div");
}

當document使用次數比較少時,可能無所謂,但是若是在一個函數的循環中大量使用document,咱們能夠提早將document變成局部變量。

來看看jquery怎麼寫的:

(function(window, undefined) {
     var jQuery = function() {}
     // ...
     window.jQuery = window.$ = jQuery;
})(window);

這樣寫的優點:

一、window和undefined都是爲了減小變量查找所通過的scope做用域。當window經過傳遞給閉包內部以後,在閉包內部使用它的時候,能夠把它當成一個局部變量,顯然比原先在window scope下查找的時候要快一些。(原來的window處於做用域鏈的最頂端,查找速度慢)

二、在jquery壓縮版本jquery.min.js中能夠將局部變量window替換成單個字母,減少文件大小,提升加載速度

三、undefined也是JavaScript中的全局屬性。將undefined做爲參數傳遞給閉包,由於沒給它傳遞值,它的值就是undefined,這樣閉包內部在使用它的時候就能夠把它當作局部變量使用,從而提升查找速度。undefined並非JavaScript的保留字或者關鍵字。

四、undefined在某些低版本的瀏覽器(例如IE八、IE7)中值是能夠被修改的(在ECMAScript3中,undefined是可讀/寫的變量,能夠給它賦任意值,這個錯誤在ECMAScript5中作了修正),將undefined做爲參數而且不給它傳值能夠防止因undefined的值被修改而產生的錯誤。

 

3、避免增加做用域鏈

在JavaScript中,有兩種語句能夠臨時增長做用域鏈:with、try-catch

with可使對象的屬性能夠像全局變量來使用,它其實是將一個新的變量對象添加到執行環境做用域的頂部,這個變量對象包含了指定對象的全部屬性,所以能夠直接訪問。

這樣看似很方便,可是增加了做用域鏈,原來函數中的局部變量不在處於做用域鏈的頂端,所以在訪問這些變量的時候要查找到第二層才能找到它。當with語句塊之行結束後,做用域鏈將回到原來的狀態。鑑於with的這個缺點,因此不推薦使用。

try-catch中的catch從句和with相似,也是在做用域鏈的頂端增長了一個對象,該對象包含了由catch指定命名的異常對象。可是由於catch語句只有在放生錯誤的時候才執行,所以影響比較少。

 

4、字符串連接優化

因爲字符串是不可變的,因此在進行字符串鏈接時,須要建立臨時字符串。頻繁建立、銷燬臨時字符串會致使性能低下。

固然,這個問題在新版本瀏覽器包括IE8+中都獲得了優化,因此不須要擔憂

在低版本瀏覽器(IE六、IE7)中,咱們能夠種數組的join方法來代替。

var temp = [];
var i = 0;
temp[i++] = "Hello";
temp[i++] = " ";
temp[i++] ="everyone";

var outcome = temp.join("");

 

5、條件判斷

當出現條件判斷時,咱們採用什麼樣的結構才能使性能最優?

if(val == 0) {
    return v0;
}else if(val == 1) {
    return v1;
}else if(val == 2) {
    return v2;
}else if(val == 3) {
    return v3;
}else if(val == 4) {
    return v4;
}

當條件分支比較多時,咱們能夠斟酌哪一種條件出現的機率比較大,並將對應的語句放在最上面,這樣能夠減小判斷次數。

使用switch語句,新版的瀏覽器基本上都對switch作了優化,這樣層數比較深時,性能比if會更好

使用數組:

var v = [v0,v1,v2,v3,v4];
return v[valeue];

要求:對應的結果是單一值,而不是一系列操做

另外,其餘方面的優化,譬如

if(condition1) {
    return v1;
}
else {
    return v2
}
// 改爲
if(condition1) {
    return v1;
}
return v2;

 

6、快速循環

一、循環總次數使用局部變量

for( var i = 0;i < arr.length;i++) {

}
// 改爲
var len = arr.length;
for( var i = 0;i < len;i++) {

}

這樣就避免了每次循環的屬性查找。這點尤爲重要,由於在進行dom操做時,不少人會這樣寫:

var divList = document.getElementsByTagName("div");
for( var i = 0;i < divList.length;i++) {

}

查找DOM元素的屬性是相對耗時的,因此應該避免這種寫法。

二、若是能夠,遞減代替遞增

for(var i = 0;i < arr.length;i++) {

}
// 改爲
for(var i = arr.length - 1;i--;) {

}

var i = 0;
while(i < arr.length) {
    i++;
}
// 改爲
var i = arr.length - 1;
while(i--) {

}

i=0的時候會直接跳出,循環次數比較多時仍是頗有用的。

 

7、展開循環

var i = arr.length - 1;
while(i--) {
    dosomething(arr[i]);
}

遇到這樣的狀況時,執行一次循環的時候咱們能夠選擇不止執行一次函數。

var interations = Math.floor(arr.length / 8);
var left = arr.length % 8;
var  i = 0;

if(left) {
    do {
        dosomething(arr[i++]);
    } while(--left);
}
do {
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
} while(--interations);

當遇到大數組,減小循環的開銷,性能不就提上去了嘛。(至於爲何是每次循環,調8次函數,大牛測出來的,這樣達到最佳)

 

8、高效存取數據

JavaScript中4種地方能夠存取數據:

字面量值;變量;數組元素;對象屬性

字面量值和變量中存取數據是最快的,從數組元素和對象屬性中存取數據相對較慢,而且隨着深度增長,存取速度會愈來愈慢,譬如obj.item.value就比obj.item慢。

某些狀況下咱們能夠將對象、數組屬性存成局部變量來提升速度,譬如:

for( var i = 0;i < arr.length;i++) {

}
// 改爲
var len = arr.length;
for( var i = 0;i < len;i++) {

}
var divList = document.getElementsByTagName("div");
for( var i = 0;i < divList.length;i++) {

}
// 改爲
// 
var divList = document.getElementsByTagName("div");
for( var i = 0,len = divList.length;i < len;i++) {

}

 

9、事件委託

事件委託就是利用冒泡的原理,將本來應該添加在某些元素身上的監聽事件,添加到其父元素身上,來達到提升性能的效果。

舉個栗子:

<div>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>
</div>
<script>
window.onload = function() {
    var ul = document.getElementsByTagName('ul')[0];
    var liList = document.getElementsByTagName('li');

    for(var i = 0,len = liList.length;i < len;i++) {
        liList[i].onclick = function() {
            alert(this.innerHTML);
        }
    }
}
</script> 

這樣咱們就爲每一個li添加了監聽事件了。

顯然,咱們經過循環爲每一個li添加監聽事件是不優化的。這樣不只浪費了內存,在新的li加入的時候咱們還要從新爲它添加監聽事件。

咱們能夠這樣寫:

<div>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>
</div>
<script>
window.onload = function() {
    var ul = document.getElementsByTagName('ul')[0];
    var liList = document.getElementsByTagName('li');

    ul.onclick = function(e) {
        var e = e || window.event;
        var target = e.target || e.srcElement;

        if(target.nodeName.toLowerCase() == "li") {
            alert(target.innerHTML);
        }
    }
}
</script>

這樣寫的好處:

只添加一個監聽事件,節省了內存;新加入li的時候咱們也不用爲它單獨添加監聽事件;在頁面中添加事件處理程序所需的時候更少,由於咱們只須要爲一個DOM元素添加事件處理程序。

 

若是是原創文章,轉載請註明出處:http://www.cnblogs.com/MarcoHan

 

最後,提一點不會提升性能的建議

if( 2 == value) {

}

相似這種判斷的時候推薦常量放在左邊,這樣就能夠預防相似 if( value = 2){} 的錯誤了,由於若是少寫了一個等號, if( value = 2) {} 是合法的語句,並且代碼量變大的時候不容易檢查出來。if( 2 = value) {} 這樣少寫了等號JavaScript引擎會直接報錯,咱們就能夠愉快地改過來了。(只是建議)

相關文章
相關標籤/搜索