吹毛求疵的追求優雅高性能JavaScript

我的博客,點擊查看目錄,喜歡能夠關注一下.javascript

1.從[]==![]爲true來剖析JavaScript各類蛋疼的類型轉換css

2.吹毛求疵的追求優雅高性能JavaScripthtml

李小龍說過:"天下武功,無堅不摧,惟快不破".(真的說過嗎?)
我想說的是:"世間網站,完美體驗,惟快不破".(這個我認可我說過.)前端

俗話說,時間就是生命,時間就是金錢,時間就是一切,人人都不想把時間白白浪費,一個網站,最重要的就是體驗,而網站好很差最直觀的感覺就是這個網站打開速度快不快,卡不卡.vue

當打開一個購物網站卡出翔,慢的要死,是否是如同心塞同樣的感覺,藍瘦香菇,想買個心愛的寶貝都不能買,內心想這尼瑪什麼玩意.java

那麼如何讓咱們的網站給用戶最佳的體驗呢?大環境咱們不說,什麼網絡啊,瀏覽器性能啊,這些咱們沒法改變,咱們能改變的就是咱們碼農能創造的,那就是代碼的性能.代碼精簡,執行速度快,嵌套層數少等等都是咱們能夠着手優化注意的地方.node

恰巧最近剛看完《高性能JavaScript》收穫頗豐,今天就以點帶面從追求高性能JavaScript的目的書寫的代碼來感覺一下速度提高帶來的體驗,從編程實踐,代碼優化的角度總結一下本身平時遇到、書中以及其餘地方看到有關提升JavaScript性能的例子,其餘關於加載和執行,數據存取,瀏覽器中的DOM,算法和流程控制,字符串和正則表達式,快速響應的用戶界面,Ajax這些大範圍的方向這裏就很少加闡述.咱們從代碼自己出發,用數聽說話,挖掘那些細思極恐的效率.有的提高可能微不足道,可是全部的微不足道彙集在一塊兒就是一個從量到質變.git

比較寬泛的闡釋高性能JavaScript,從大致上瞭解提升JavaScript性能的有幾個大方面,能夠閱讀這兩篇文章做詳細瞭解:github

高性能JavaScript讀書筆記.
高性能javascript小結web

本文只從代碼和數據上闡述具體說明如何一步步提升JavaScript的性能.

Javascript是一門很是靈活的語言,咱們能夠爲所欲爲的書寫各類風格的代碼,不一樣風格的代碼也必然也會致使執行效率的差別,做用域鏈、閉包、原型繼承、eval等特性,在提供各類神奇功能的同時也帶來了各類效率問題,用之不慎就會致使執行效率低下,開發過程當中零零散散地接觸到許多提升代碼性能的方法,整理一下平時比較常見而且容易規避的問題。

算法和流程控制的優化

循壞

你一天(一週)內寫了多少個循環了?
咱們先以最簡單的循環入手做爲切入點,這裏咱們只考慮單層循環以及比較不一樣循環種類和不一樣流程控制的效率.

測試數據:[1,2,3,...,10000000]
測試依據:數組[1,2,3,''',10000000]的累加所須要的時間
測試環境:node版本v6.9.4環境的v8引擎

爲何不在瀏覽器控制檯測試?
首先不一樣瀏覽器的不一樣版本性能可能就不同,這裏爲了統一,我選擇了node環境,爲何不選擇瀏覽器而選擇了node環境測試,這是由於瀏覽器的一部分緣由.

由於用控制檯是測不出性能的,由於控制檯本質上是個套了一大堆安全機制的eval,它的沙盒化程度很高。這裏咱們就一個簡單的例子來對比一下,瀏覽器和node環境一樣的代碼的執行效率.

測試的數組代碼:

var n = 10000000;
// 準備待測數組
var arr = [];
for(var count=0;count<n;count++){
    arr[count] = 1;
}

// for 測試
console.time('for');
for(var i=0;i<arr.length;i++){
    arr[i];
}
console.timeEnd('for');

就想簡單測試一下這段生成待測數組所消耗時間的對比:

這是最新谷歌瀏覽器控制檯的執行結果:

時間大約在28ms左右.

咱們再來在node環境下測試一下所須要的時間:

時間穩定在7ms左右,大約3倍的差距,一樣都是v8引擎,瀏覽器就存在很明顯的差距,這是因爲瀏覽器的機制有關,瀏覽器要處理的事情遠遠比單純在node環境下執行代碼處理的事情多,因此用瀏覽器測試性能沒有在單純地node環境下靠譜.

具體細節和討論能夠參看知乎上的這篇RednaxelaFX的回答:
爲什麼瀏覽器控制檯的JavaScript引擎性能這麼差?

各個循環實現的測試代碼,每一個方法都會單獨執行,一塊兒執行會有所誤差.

// for 測試(for和while其實差很少,這裏咱們只測試for循環)
console.time('for');
for (var i = 0; i < arr.length; i++) {
    arr[i];
}
console.timeEnd('for');


// for loop測試
console.time('for');
var sum = 0;
for (var i = 0; i < arr.length; i++) {
    sum += arr[i];
}
console.timeEnd('for');


// for loop緩存測試
console.time('for cache');
var sum = 0;
var len = arr.length;
for (var i = 0; i < len; i++) {
    sum += arr[i];
}
console.timeEnd('for cache');

// for loop倒序測試
console.time('for reverse');
var sum = 0;
var len = arr.length;
for (i = len-1;i>0; i--) {
    sum += arr[i];
}
console.timeEnd('for reverse');


//forEach測試
console.time('forEach');
var sum = 0;
arr.forEach(function(ele) {
    sum += ele;
})
//這段代碼看起來更加簡潔,但這種方法也有一個小缺陷:你不能使用break語句中斷循環,也不能使用return語句返回
到外層函數。
console.timeEnd('forEach');


//ES6的for of測試
console.time('for of');
var sum = 0;
for (let i of arr) {
    sum += i;
}
console.timeEnd('for of');
//這是最簡潔、最直接的遍歷數組元素的語法
//這個方法避開了for-in循環的全部缺陷
//與forEach()不一樣的是,它能夠正確響應break、continue和return語句


// for in 測試
console.time('for in');
var sum=0;
for(var i in arr){
    sum+=arr[i];
}
console.timeEnd('for in');
這是最簡潔、最直接的遍歷數組元素的語法
這個方法避開了for-in循環的全部缺陷
與forEach()不一樣的是,它能夠正確響應break、continue和return語句

最後在node環境下各自所花費的不一樣時間:

循環類型 耗費時間(ms)
for 約11.998
for cache 約10.866
for 倒序 約11.230
forEach 約400.245
for in 約2930.118
for of 約320.921

從上面的表格統計比較能夠看出,前三種原始的for循壞一個檔次,而後forEach和for of也基本屬於一個檔次,for of的執行速度稍微高於forEach,最後最慢的就是for in循環了,差的不是幾十倍的關係了.

看起來好像是那麼回事哦!

一樣是循壞爲何會有如此大的懸殊呢?咱們來稍微分析一下其中的個別原因致使這樣的差別.

for in 通常是用在對象屬性名的遍歷上的,因爲每次迭代操做會同時搜索實例自己的屬性以及原型鏈上的屬性,因此效率確定低下.

for...in 實際上效率是最低的。這是由於 for...in 有一些特殊的要求,具體包括:

1. 遍歷全部屬性,不只是 ownproperties 也包括原型鏈上的全部屬性。
2. 忽略 enumerable 爲 false 的屬性。
3. 必須按特定順序遍歷,先遍歷全部數字鍵,而後按照建立屬性的順序遍歷剩下的。

這裏既然扯到對象的遍歷屬性,就順便扯一扯幾種對象遍歷屬性不一樣區別,爲何for...in性能這麼差,算是一個延伸吧,反正我發現寫一篇博客能夠延伸不少東西,本身也能夠學到不少,還能夠鞏固本身以前學過可是遺忘的一些東西,算是溫故而知新.

遍歷數組屬性目前我知道的有:for-in循環、Object.keys()Object.getOwnPropertyNames(),那麼三種到底有啥區別呢?

for-in循環:會遍歷對象自身的屬性,以及原型屬性,包括enumerable 爲 false(不可枚舉屬性);
Object.keys():能夠獲得自身可枚舉的屬性,但得不到原型鏈上的屬性;
Object.getOwnPropertyNames():能夠獲得自身全部的屬性(包括不可枚舉),但得不到原型鏈上的屬性,Symbols屬性
也得不到.

Object.defineProperty顧名思義,就是用來定義對象屬性的,vue.js的雙向數據綁定主要在gettersetter函數裏面插入一些處理方法,當對象被讀寫的時候處理方法就會被執行了。 關於這些方法和屬性的更具體解釋,能夠看MDN上的解釋(戳我);

簡單看一個小demo例子加深理解,對於Object.defineProperty屬性不太明白,能夠看看上面介紹的文檔學習補充一下.

'use strict';
class A {
    constructor() {
        this.name = 'jawil';
    }
    getName() {}
}
class B extends A {
    constructor() {
            super();
            this.age = 22;
        }
        //getAge不可枚舉
    getAge() {}
        [Symbol('fullName')]() {

        }
}
B.prototype.get = function() {

}
var b = new B();

//設置b對象的info屬性的enumerable: false,讓其不能枚舉.
Object.defineProperty(b, 'info', {
    value: 7,
    writable: true,
    configurable: true,
    enumerable: false
});

//Object能夠獲得自身可枚舉的屬性,但得不到原型鏈上的屬性
console.log(Object.keys(b)); //[ 'name', 'age' ]


//Object可A以獲得自身全部的屬性(包括不可枚舉),但得不到原型鏈上的屬性,Symbols屬性也得不到
console.log(Object.getOwnPropertyNames(b)); //[ 'name', 'age', 'info' ]

for (var attr in b) {
    console.log(attr);//name,age,get
}

//in會遍歷對象自身的屬性,以及原型屬性
console.log('getName' in b); //true

從這裏也能夠看出爲何for...in性能這麼慢,由於它要遍歷自身的屬性和原型鏈上的屬性,這無疑就增長了全部沒必要要的額外開銷.

目前絕大部分開源軟件都會在for loop中緩存數組長度,由於普通觀點認爲某些瀏覽器Array.length每次都會從新計算數組長度,所以一般用臨時變量來事先存儲數組長度,以此來提升性能.

而forEach是基於函數的迭代(須要特別注意的是全部版本的ie都不支持,若是須要能夠用JQuery等庫),對每一個數組項調用外部方法所帶來的開銷是速度慢的主要緣由.

總結:

1.能用for緩存的方法循環就用for循壞,性能最高,寫起來繁雜;
2.不追求極致性能的狀況下,建議使用forEach方法,乾淨,簡單,易讀,短,沒有中間變量,沒有成堆的分號,簡單很是
優雅;
3.想嚐鮮使用ES6語法的話,不考慮兼容性狀況下,推薦使用for of方法,這是最簡潔、最直接的遍歷數組元素的語法,該方
法避開了for-in;循環的全部缺陷與forEach()不一樣的是,它能夠正確響應break、continue和return語句.
4.能避免for in循環儘可能避免,太消費性能,太費時間,數組循環不推薦使用.

條件語句

常見的條件語句有if-elseswitch-case,那麼何時用if-else,何時用switch-case語句呢?
  
咱們先來看個簡單的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 {
    return result10;
}

最壞的狀況下(value=10)咱們可能要作10次判斷才能返回正確的結果,那麼咱們怎麼優化這段代碼呢?一個顯而易見的優化策略是將最可能的取值提早判斷,好比value最可能等於5或者10,那麼將這兩條判斷提早。可是一般狀況下咱們並不知道(最可能的選擇),這時咱們能夠採起二叉樹查找策略進行性能優化。

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 result6;
        } else {
            return result7;
        }
    } else {
        if (value == 8){
            return result8;
        } else if (value == 9){
            return result9;
        } else {
            return result10;
        }
    }
}

這樣優化後咱們最多進行4次判斷便可,大大提升了代碼的性能。這樣的優化思想有點相似二分查找,和二分查找類似的是,只有value值是連續的數字時才能進行這樣的優化。可是代碼這樣寫的話不利於維護,若是要增長一個條件,或者多個條件,就要重寫不少代碼,這時switch-case語句就有了用武之地。

將以上代碼用switch-case語句重寫:

switch(value){
    case 0:
        return result0;
    case 1:
        return result1;
    case 2:
        return result2;
    case 3:
        return result3;
    case 4:
        return result4;
    case 5:
        return result5;
    case 6:
        return result6;
    case 7:
        return result7;
    case 8:
        return result8;
    case 9:
        return result9;
    default:
        return result10;
}

swtich-case語句讓代碼顯得可讀性更強,並且swtich-case語句還有一個好處是若是多個value值返回同一個結果,就不用重寫return那部分的代碼。通常來講,當case數達到必定數量時,swtich-case語句的效率是比if-else高的,由於switch-case採用了branch table(分支表)索引來進行優化,固然各瀏覽器的優化程度也不同。

相對來講,下面幾種狀況更適合使用switch結構:

枚舉表達式的值。這種枚舉是能夠指望的、平行邏輯關係的。

表達式的值具備離散性,不具備線性的非連續的區間值。

表達式的值是固定的,不是動態變化的。

表達式的值是有限的,而不是無限的,通常狀況下表達式應該比較少。

表達式的值通常爲整數、字符串等類型的數據。

而if結構則更適合下面的一些狀況:

具備複雜的邏輯關係。

表達式的值具備線性特徵,如對連續的區間值進行判斷。

表達式的值是動態的。

測試任意類型的數據。

除了if-elseswtich-case外,咱們還能夠採用查找表。

var results = [result0, result1, result2, result3, result4, result5, result6, result7, 
result8, result9, result10];

//return the correct result
return results[value];

  當數據量很大的時候,查找表的效率一般要比if-else語句和swtich-case語句高,查找表能用數字和字符串做爲索引,而若是是字符串的狀況下,最好用對象來代替數組。固然查找表的使用是有侷限性的,每一個case對應的結果只能是一個取值而不能是一系列的操做。
  
  從根源上分析if elseswitch的效率,以前只知道if elseswitch算法實現不一樣,但具體怎麼樣就不清楚了,爲了刨根問底,翻閱了不少資料,但無奈找到了緣由我仍是不太懂,只知道就是這麼回事,不懂彙編,懂底層彙編的大神還望多加指點.
  
  想要深刻從彙編的角度瞭解能夠看看這篇文章:switch...case和if...else效率比較
  
  學前端的我想問你彙編是啥?能吃嗎?
  
    
  在選擇分支較多時,選用switch...case結構會提升程序的效率,但switch不足的地方在於只能處理字符或者數字類型的變量,if...else結構更加靈活一些,if...else結構能夠用於判斷表達式是否成立,好比if(a+b>c),if...else的應用範圍更廣,switch...case結構在某些狀況下能夠替代if...else結構。
  
小結:

1. 當只有兩個case或者case的value取值是一段連續的數字的時候,咱們能夠選擇if-else語句;
2. 當有3~10個case數而且case的value取值非線性的時候,咱們能夠選擇switch-case語句;
3. 當case數達到10個以上而且每次的結果只是一個取值而不是額外的JavaScript語句的時候,咱們能夠選擇查找表.

事件委託減小循環綁定的事件

什麼是事件委託:通俗的講,事件就是onclick,onmouseover,onmouseout,等就是事件,委託呢,就是讓別人來作,這個事件原本是加在某些元素上的,然而你卻加到別人身上來作,完成這個事件。

也就是:利用冒泡的原理,把事件加到父級上,觸發執行效果。
好處呢:
1.提升性能。
2.新添加的元素還會有以前的事件。

試想一下,一個頁面上ul的每個li標籤添加一個事件,咱們會不會給每個標籤都添加一個onclick呢。 當頁面中存在大量元素都須要綁定同一個事件處理的時候,這種狀況可能會影響性能,不只消耗了內存,還多循環時間。每綁定一個事件都加劇了頁面或者是運行期間的負擔。對於一個富前端的應用,交互重的頁面上,過多的綁定會佔用過多內存。 一個簡單優雅的方式就是事件委託。它是基於事件的工做流:逐層捕獲,到達目標,逐層冒泡。既然事件存在冒泡機制,那麼咱們能夠經過給外層綁定事件,來處理全部的子元素出發的事件。
一個事件委託的簡單實現:

document.getElementById('ulId').onclick = function(e) {
    var e = e || window.event;
    var target = e.target || e.srcElement; //兼容舊版本IE和現代瀏覽器
    if (target.nodeName.toLowerCase() !== 'ul') {
        return;
    }
    console.log(target.innerHTML);
}

咱們能夠看一個例子:須要觸發每一個li來改變他們的背景顏色。

<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

首先想到最直接的實現:

window.onload = () => {
    let oUl = document.querySelector("#ul");
    let aLi = oUl.querySelectorAll("li");
    Array.from(aLi).forEach(ele => {
        ele.onmouseover = function() {
            this.style.background = "red";
        }
        ele.onmouseout = function() {
            this.style.background = "";
        }
    })
}

這樣咱們就能夠作到li上面添加鼠標事件。

可是若是說咱們可能有不少個li用for循環的話就比較影響性能。

下面咱們能夠用事件委託的方式來實現這樣的效果。html不變

window.onload = () => {
    let oUl = document.querySelector("#ul");
    /*
    這裏要用到事件源:event 對象,事件源,無論在哪一個事件中,只要你操做的那個元素就是事件源。
    ie:window.event.srcElement
    標準下:event.target
    nodeName:找到元素的標籤名
    */
    //雖然習慣用ES6語法寫代碼,這裏事件仍是兼容一下IE吧
    oUl.onmouseover = ev => {
        ev = ev || window.event;
        let target = ev.target || ev.srcElement;
        //console.log(target.innerHTML);
        if (target.nodeName.toLowerCase() === "li") {
            target.style.background = "red";
        }
    }
    oUl.onmouseout = ev=> {
        ev = ev || window.event;
        let target = ev.target || ev.srcElement;
        //console.log(target.innerHTML);
        if (target.nodeName.toLowerCase() == "li") {
            target.style.background = "";
        }
    }
}

好處2,新添加的元素還會有以前的事件。

咱們還拿這個例子看,可是咱們要作動態的添加li。點擊button動態添加li

<input type="button" id="btn" />
<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

不用事件委託咱們會這樣作:

window.onload = () => {
    let oUl = document.querySelector("#ul");
    let aLi = oUl.querySelectorAll("li");
    let oBtn = document.querySelector("#btn");
    let iNow = 4;
    //剛纔用forEach實現,如今就用性能最高的for實現,把剛纔學的溫習一下

    for (let i = 0, len = aLi.length; i < len; i++) {
        aLi[i].onmouseover = function() {
            this.style.background = "red";
        }
        aLi[i].onmouseout = function() {
            this.style.background = "";
        }
    }

    oBtn.onclick = function() {
        iNow++;
        let oLi = document.createElement("li");
        oLi.innerHTML = 1* iNow;
        oUl.appendChild(oLi);
    }

}

這樣作咱們能夠看到點擊按鈕新加的li上面沒有鼠標移入事件來改變他們的背景顏色。

由於點擊添加的時候for循環已經執行完畢。

那麼咱們用事件委託的方式來作。就是html不變

window.onload = () => {
    let oUl = document.querySelector("#ul");
    let oBtn = document.querySelector("#btn");
    let iNow = 4;
    oUl.onmouseover = ev => {
        ev = ev || window.event;
        let target = ev.target || ev.srcElement;
        if (target.nodeName.toLowerCase() === "li") {
            target.style.background = "red";
        }
    }
    oUl.onmouseout = ev => {
        ev = ev || window.event;
        let target = ev.target || ev.srcElement;
        if (target.nodeName.toLowerCase() == "li") {
            target.style.background = "";
        }
    }
    oBtn.onclick = function() {
        iNow++;
        let oLi = document.createElement("li");
        oLi.innerHTML = 1111 * iNow;
        oUl.appendChild(oLi);
    }
}

更快速的數據訪問

對於瀏覽器來講,一個標識符所處的位置越深,去讀寫他的速度也就越慢(對於這點,原型鏈亦是如此)。這個應該不難理解,簡單比喻就是:雜貨店離你家越遠,你去打醬油所花的時間就越長... 熊孩子,打個醬油那麼久,菜早燒焦了 -.-~

咱們在編碼過程當中多多少少會使用到一些全局變量(window,document,自定義全局變量等等),瞭解javascript做用域鏈的人都知道,在局部做用域中訪問全局變量須要一層一層遍歷整個做用域鏈直至頂級做用域,而局部變量的訪問效率則會更快更高,所以在局部做用域中高頻率使用一些全局對象時能夠將其導入到局部做用域中,例如:
對比看看:

//修改前
 function showLi(){
   var i = 0;
   for(;i<document.getElementsByTagName("li").length;i++){  //一次訪問document
     console.log(i,document.getElementsByTagName("li")[i]); //三次訪問document
   };
 };
 //修改後
 function showLi(){
   var li_s = document.getElementsByTagName("li"); //一次訪問document
   var i = 0;
   for(;i<li_s.length;i++){
     console.log(i,li_s[i]); //三次訪問局部變量li_s
   };
 };

再來看看兩個簡單的例子;

//一、做爲參數傳入模塊  
 (function(window,$){  
     var xxx = window.xxx;  
     $("#xxx1").xxx();  
     $("#xxx2").xxx();  
 })(window,jQuery);  
   
 //二、暫存到局部變量  
 function(){  
    var doc = document;  
    var global = window.global;  
}

eval以及類eval問題

咱們都知道eval能夠將一段字符串當作js代碼來執行處理,聽說使用eval執行的代碼比不使用eval的代碼慢100倍以上(具體效率我沒有測試,有興趣同窗能夠測試一下),前面的瀏覽器控制檯效率低下也提到eval這個問題.

JavaScript 代碼在執行前會進行相似「預編譯」的操做:首先會建立一個當前執行環境下的活動對象,並將那些用 var 
申明的變量設置爲活動對象的屬性,可是此時這些變量的賦值都是 undefined,並將那些以 function 定義的函數也
添加爲活動對象的屬性,並且它們的值正是函數的定義。可是,若是你使用了「eval」,則「eval」中的代碼(實際上爲字
符串)沒法預先識別其上下文,沒法被提早解析和優化,即沒法進行預編譯的操做。因此,其性能也會大幅度下降

對上面js的預編譯,活動對象等一些列不太明白的童鞋.看完這篇文章惡補一下,我想你會收穫不少:前端基礎進階(三):變量對象詳解

其實如今你們通常都不多會用eval了,這裏我想說的是兩個類eval的場景(new Function{},setTimeout,
setInterver)

setTimtout("alert(1)",1000);  
 
setInterver("alert(1)",1000);  
 
(new Function("alert(1)"))();

上述幾種類型代碼執行效率都會比較低,所以建議直接傳入匿名方法、或者方法的引用給setTimeout方法.

DOM操做的優化

衆所周知的,DOM操做遠比javascript的執行耗性能,雖然咱們避免不了對DOM進行操做,但咱們能夠儘可能去減小該操做對性能的消耗。

爲何操做DOM這麼耗費性能呢?

瀏覽器一般會把js和DOM分開來分別獨立實現。 
舉個栗子冷知識,在IE中,js的實現名爲JScript,位於jscript.dll文件中;DOM的實現則存在另外一個庫中,名爲mshtml.dll(Trident)。
Chrome中的DOM實現爲webkit中的webCore,但js引擎是Google本身研發的V8。 
Firefox中的js引擎是SpiderMonkey,渲染引擎(DOM)則是Gecko。

DOM,天生就慢

前面的小知識中說過,瀏覽器把實現頁面渲染的部分和解析js的部分分開來實現,既然是分開的,一旦二者須要產生鏈接,就要付出代價。

兩個例子:

  1. 小明和小紅是兩個不一樣學校的學生,兩我的家裏經濟條件都不太好,買不起手機(好尷尬的設定Orz...),因此只能經過寫信來互相交流,這樣的過程確定比他倆面對面交談時所須要花費的代價大(額外的事件、寫信的成本等)。

  2. 官方例子:把DOM和js(ECMAScript)各自想象爲一座島嶼,它們之間用收費橋進行鏈接。ECMAScript每次訪問DOM,都要途徑這座橋,並交納「過橋費」。訪問DOM的次數越多,費用也就越高。

所以,推薦的作法是:儘量的減小過橋的次數,努力待在ECMAScript島上。

讓咱們經過一個最簡單的代碼解釋這個問題:

function innerLi_s(){
   var i = 0;
   for(;i<20;i++){
     document.getElementById("Num").innerHTML="A"; 
     //進行了20次循環,每次又有2次DOM元素訪問:一次讀取innerHTML的值,一次寫入值
   };
 };

針對以上方法進行一次改寫:

function innerLi_s(){
   var content ="";
   var i = 0;
   for(;i<20;i++){
     content += "A"; //這裏只對js的變量循環了20次
   };
   document.getElementById("Num").innerHTML += content; 
   //這裏值進行了一次DOM操做,又分2次DOM訪問:一次讀取innerHTML的值,一次寫入值
 };

減小頁面的重排(Reflows)和重繪(Repaints)

簡單說下什麼是重排和重繪:
瀏覽器下載完HTMl,CSS,JS後會生成兩棵樹:DOM樹和渲染樹。 當Dom的幾何屬性發生變化時,好比Dom的寬高,或者顏色,position,瀏覽器須要從新計算元素的幾何屬性,而且從新構建渲染樹,這個過程稱之爲重繪重排。

元素佈局的改變或內容的增刪改或者瀏覽器窗口尺寸改變都將會致使重排,而字體顏色或者背景色的修改則將致使重繪。
對於相似如下代碼的操做,聽說現代瀏覽器大多進行了優化(將其優化成1次重排版):

//修改前
 var el = document.getElementById("div");
 el.style.borderLeft = "1px"; //1次重排版
 el.style.borderRight = "2px"; //又1次重排版
 el.style.padding = "5px"; //還有1次重排版
 //修改後
 var el = document.getElementById("div");
 el.style.cssText = "border-left:1px;border-right:2px;padding:5px"; //1次重排版

針對多重操做,如下三種方法也能夠減小重排版和重繪的次數:

  1. Dom先隱藏,操做後再顯示 2次重排 (臨時的display:none);

  2. document.createDocumentFragment() 建立文檔片斷處理,操做後追加到頁面 1次重排;

  3. var newDOM = oldDOM.cloneNode(true)建立Dom副本,修改副本後oldDOM.parentNode.replaceChild
    (newDOM,oldDOM)覆蓋原DOM 2次重排

  4. 若是是動畫元素的話,最好使用絕對定位以讓它不在文檔流中,這樣的話改變它的位置不會引發頁面其它元素重排

更多DOM優化的細節以及關於瀏覽器頁面的重排(Reflows)和重繪(Repaints)的概念和優化請參考:天生就慢的DOM如何優化?,花10+分鐘閱讀,你會受益不淺.

儘可能少去改變做用域鏈

  • 使用with

  • try catch

我瞭解到的JavaScript中改變做用域鏈的方式只有兩種1)使用with表達式 2)經過捕獲異常try catch來實現

可是with是你們都深惡痛絕的影響性能的表達式,由於咱們徹底能夠經過使用一個局部變量的方式來取代它(由於with的原理是它的改變做用域鏈的同時須要保存不少信息以保證完成當前操做後恢復以前的做用域鏈,這些就明顯的影響到了性能)

try catch中的catch子句一樣能夠改變做用域鏈。當try塊發生錯誤時,程序自動轉入catch塊並將異常對象推入做用域鏈前端的一個可變對象中,也就是說在catch塊中,函數全部的局部變量已經被放在第二個做用域鏈對象中,可是catch子句執行完成以後,做用域鏈就會返回到原來的狀態。應該最小化catch子句來保證代碼性能,若是知道錯誤的概念很高,咱們應該儘可能修正錯誤而不是使用try catch.

最後

雖然說現代瀏覽器都已經作的很好了,可是本獸以爲這是本身對代碼質量的一個追求。而且可能一個點或者兩個點不注意是不會產生多大性能影響,可是從多個點進行優化後,可能產生的就會質的飛躍了
JavaScript 總結的這幾個提升性能知識點,但願你們緊緊掌握。

參考文章:

高性能JavaScript循環語句和流程控制
if else 和 switch的效率
switch...case和if...else效率比較
JavaScript提升性能知識點彙總
JavaScript執行效率小結
天生就慢的DOM如何優化?
編寫高性能的JavaScript代碼

相關文章
相關標籤/搜索