成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

吹毛求疵的追求優(yōu)雅高性能JavaScript

saucxs / 3167人閱讀

摘要:這樣優(yōu)化后我們最多進(jìn)行次判斷即可,大大提高了代碼的性能。表達(dá)式的值具有離散性,

個(gè)人博客,點(diǎn)擊查看目錄,喜歡可以關(guān)注一下.

1.從[]==![]為true來剖析JavaScript各種蛋疼的類型轉(zhuǎn)換

2.吹毛求疵的追求優(yōu)雅高性能JavaScript

李小龍說過:"天下武功,無堅(jiān)不摧,唯快不破".(真的說過嗎?)
我想說的是:"世間網(wǎng)站,完美體驗(yàn),唯快不破".(這個(gè)我承認(rèn)我說過.)

俗話說,時(shí)間就是生命,時(shí)間就是金錢,時(shí)間就是一切,人人都不想把時(shí)間白白浪費(fèi),一個(gè)網(wǎng)站,最重要的就是體驗(yàn),而網(wǎng)站好不好最直觀的感受就是這個(gè)網(wǎng)站打開速度快不快,卡不卡.

當(dāng)打開一個(gè)購(gòu)物網(wǎng)站卡出翔,慢的要死,是不是如同心塞一樣的感受,藍(lán)瘦香菇,想買個(gè)心愛的寶貝都不能買,心里想這尼瑪什么玩意.

那么如何讓我們的網(wǎng)站給用戶最佳的體驗(yàn)?zāi)?大環(huán)境我們不說,什么網(wǎng)絡(luò)啊,瀏覽器性能啊,這些我們無法改變,我們能改變的就是我們碼農(nóng)能創(chuàng)造的,那就是代碼的性能.代碼精簡(jiǎn),執(zhí)行速度快,嵌套層數(shù)少等等都是我們可以著手優(yōu)化注意的地方.

恰巧最近剛看完《高性能JavaScript》收獲頗豐,今天就以點(diǎn)帶面從追求高性能JavaScript的目的書寫的代碼來感受一下速度提升帶來的體驗(yàn),從編程實(shí)踐,代碼優(yōu)化的角度總結(jié)一下自己平時(shí)遇到、書中以及其他地方看到有關(guān)提高JavaScript性能的例子,其他關(guān)于加載和執(zhí)行,數(shù)據(jù)存取,瀏覽器中的DOM,算法和流程控制,字符串和正則表達(dá)式,快速響應(yīng)的用戶界面,Ajax這些大范圍的方向這里就不多加闡述.我們從代碼本身出發(fā),用數(shù)據(jù)說話,挖掘那些細(xì)思極恐的效率.有的提升可能微不足道,但是所有的微不足道聚集在一起就是一個(gè)從量到質(zhì)變.

比較寬泛的闡釋高性能JavaScript,從大體上了解提高JavaScript性能的有幾個(gè)大方面,可以閱讀這兩篇文章作詳細(xì)了解:

高性能JavaScript讀書筆記.
高性能javascript小結(jié)

本文只從代碼和數(shù)據(jù)上闡述具體說明如何一步步提高JavaScript的性能.

Javascript是一門非常靈活的語言,我們可以隨心所欲的書寫各種風(fēng)格的代碼,不同風(fēng)格的代碼也必然也會(huì)導(dǎo)致執(zhí)行效率的差異,作用域鏈、閉包、原型繼承、eval等特性,在提供各種神奇功能的同時(shí)也帶來了各種效率問題,用之不慎就會(huì)導(dǎo)致執(zhí)行效率低下,開發(fā)過程中零零散散地接觸到許多提高代碼性能的方法,整理一下平時(shí)比較常見并且容易規(guī)避的問題。

算法和流程控制的優(yōu)化 循壞

你一天(一周)內(nèi)寫了多少個(gè)循環(huán)了?
我們先以最簡(jiǎn)單的循環(huán)入手作為切入點(diǎn),這里我們只考慮單層循環(huán)以及比較不同循環(huán)種類和不同流程控制的效率.

測(cè)試數(shù)據(jù):[1,2,3,...,10000000]
測(cè)試依據(jù):數(shù)組[1,2,3,""",10000000]的累加所需要的時(shí)間
測(cè)試環(huán)境:node版本v6.9.4環(huán)境的v8引擎

為什么不在瀏覽器控制臺(tái)測(cè)試?
首先不同瀏覽器的不同版本性能可能就不一樣,這里為了統(tǒng)一,我選擇了node環(huán)境,為什么不選擇瀏覽器而選擇了node環(huán)境測(cè)試,這是因?yàn)闉g覽器的一部分原因.

因?yàn)橛每刂婆_(tái)是測(cè)不出性能的,因?yàn)榭刂婆_(tái)本質(zhì)上是個(gè)套了一大堆安全機(jī)制的eval,它的沙盒化程度很高。這里我們就一個(gè)簡(jiǎn)單的例子來對(duì)比一下,瀏覽器和node環(huán)境同樣的代碼的執(zhí)行效率.

測(cè)試的數(shù)組代碼:

var n = 10000000;
// 準(zhǔn)備待測(cè)數(shù)組
var arr = [];
for(var count=0;count

就想簡(jiǎn)單測(cè)試一下這段生成待測(cè)數(shù)組所消耗時(shí)間的對(duì)比:

這是最新谷歌瀏覽器控制臺(tái)的執(zhí)行結(jié)果:

時(shí)間大約在28ms左右.

我們?cè)賮碓?b>node環(huán)境下測(cè)試一下所需要的時(shí)間:

時(shí)間穩(wěn)定在7ms左右,大約3倍的差距,同樣都是v8引擎,瀏覽器就存在很明顯的差距,這是由于瀏覽器的機(jī)制有關(guān),瀏覽器要處理的事情遠(yuǎn)遠(yuǎn)比單純?cè)?b>node環(huán)境下執(zhí)行代碼處理的事情多,所以用瀏覽器測(cè)試性能沒有在單純地node環(huán)境下靠譜.

具體細(xì)節(jié)和討論可以參看知乎上的這篇RednaxelaFX的回答:
為何瀏覽器控制臺(tái)的JavaScript引擎性能這么差?

各個(gè)循環(huán)實(shí)現(xiàn)的測(cè)試代碼,每個(gè)方法都會(huì)多帶帶執(zhí)行,一起執(zhí)行會(huì)有所偏差.

// for 測(cè)試(for和while其實(shí)差不多,這里我們只測(cè)試for循環(huán))
console.time("for");
for (var i = 0; i < arr.length; i++) {
    arr[i];
}
console.timeEnd("for");


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


// for loop緩存測(cè)試
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倒序測(cè)試
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測(cè)試
console.time("forEach");
var sum = 0;
arr.forEach(function(ele) {
    sum += ele;
})
//這段代碼看起來更加簡(jiǎn)潔,但這種方法也有一個(gè)小缺陷:你不能使用break語句中斷循環(huán),也不能使用return語句返回
到外層函數(shù)。
console.timeEnd("forEach");


//ES6的for of測(cè)試
console.time("for of");
var sum = 0;
for (let i of arr) {
    sum += i;
}
console.timeEnd("for of");
//這是最簡(jiǎn)潔、最直接的遍歷數(shù)組元素的語法
//這個(gè)方法避開了for-in循環(huán)的所有缺陷
//與forEach()不同的是,它可以正確響應(yīng)break、continue和return語句


// for in 測(cè)試
console.time("for in");
var sum=0;
for(var i in arr){
    sum+=arr[i];
}
console.timeEnd("for in");
這是最簡(jiǎn)潔、最直接的遍歷數(shù)組元素的語法
這個(gè)方法避開了for-in循環(huán)的所有缺陷
與forEach()不同的是,它可以正確響應(yīng)break、continue和return語句

最后在node環(huán)境下各自所花費(fèi)的不同時(shí)間:

循環(huán)類型 耗費(fèi)時(shí)間(ms)
for 約11.998
for cache 約10.866
for 倒序 約11.230
forEach 約400.245
for in 約2930.118
for of 約320.921

從上面的表格統(tǒng)計(jì)比較可以看出,前三種原始的for循壞一個(gè)檔次,然后forEach和for of也基本屬于一個(gè)檔次,for of的執(zhí)行速度稍微高于forEach,最后最慢的就是for in循環(huán)了,差的不是幾十倍的關(guān)系了.

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

同樣是循壞為什么會(huì)有如此大的懸殊呢?我們來稍微分析一下其中的個(gè)別緣由導(dǎo)致這樣的差異.

for in 一般是用在對(duì)象屬性名的遍歷上的,由于每次迭代操作會(huì)同時(shí)搜索實(shí)例本身的屬性以及原型鏈上的屬性,所以效率肯定低下.

for...in 實(shí)際上效率是最低的。這是因?yàn)?for...in 有一些特殊的要求,具體包括:

1. 遍歷所有屬性,不僅是 ownproperties 也包括原型鏈上的所有屬性。
2. 忽略 enumerable 為 false 的屬性。
3. 必須按特定順序遍歷,先遍歷所有數(shù)字鍵,然后按照創(chuàng)建屬性的順序遍歷剩下的。

這里既然扯到對(duì)象的遍歷屬性,就順便扯一扯幾種對(duì)象遍歷屬性不同區(qū)別,為什么for...in性能這么差,算是一個(gè)延伸吧,反正我發(fā)現(xiàn)寫一篇博客可以延伸很多東西,自己也可以學(xué)到很多,還可以鞏固自己之前學(xué)過但是遺忘的一些東西,算是溫故而知新.

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

for-in循環(huán):會(huì)遍歷對(duì)象自身的屬性,以及原型屬性,包括enumerable 為 false(不可枚舉屬性);
Object.keys():可以得到自身可枚舉的屬性,但得不到原型鏈上的屬性;
Object.getOwnPropertyNames():可以得到自身所有的屬性(包括不可枚舉),但得不到原型鏈上的屬性,Symbols屬性
也得不到.

Object.defineProperty顧名思義,就是用來定義對(duì)象屬性的,vue.js的雙向數(shù)據(jù)綁定主要在gettersetter函數(shù)里面插入一些處理方法,當(dāng)對(duì)象被讀寫的時(shí)候處理方法就會(huì)被執(zhí)行了。 關(guān)于這些方法和屬性的更具體解釋,可以看MDN上的解釋(戳我);

簡(jiǎn)單看一個(gè)小demo例子加深理解,對(duì)于Object.defineProperty屬性不太明白,可以看看上面介紹的文檔學(xué)習(xí)補(bǔ)充一下.

"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();

//設(shè)置b對(duì)象的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會(huì)遍歷對(duì)象自身的屬性,以及原型屬性
console.log("getName" in b); //true

從這里也可以看出為什么for...in性能這么慢,因?yàn)樗闅v自身的屬性和原型鏈上的屬性,這無疑就增加了所有不必要的額外開銷.

目前絕大部分開源軟件都會(huì)在for loop中緩存數(shù)組長(zhǎng)度,因?yàn)槠胀ㄓ^點(diǎn)認(rèn)為某些瀏覽器Array.length每次都會(huì)重新計(jì)算數(shù)組長(zhǎng)度,因此通常用臨時(shí)變量來事先存儲(chǔ)數(shù)組長(zhǎng)度,以此來提高性能.

而forEach是基于函數(shù)的迭代(需要特別注意的是所有版本的ie都不支持,如果需要可以用JQuery等庫),對(duì)每個(gè)數(shù)組項(xiàng)調(diào)用外部方法所帶來的開銷是速度慢的主要原因.

總結(jié):

1.能用for緩存的方法循環(huán)就用for循壞,性能最高,寫起來繁雜;
2.不追求極致性能的情況下,建議使用forEach方法,干凈,簡(jiǎn)單,易讀,短,沒有中間變量,沒有成堆的分號(hào),簡(jiǎn)單非常
優(yōu)雅;
3.想嘗鮮使用ES6語法的話,不考慮兼容性情況下,推薦使用for of方法,這是最簡(jiǎn)潔、最直接的遍歷數(shù)組元素的語法,該方
法避開了for-in;循環(huán)的所有缺陷與forEach()不同的是,它可以正確響應(yīng)break、continue和return語句.
4.能避免for in循環(huán)盡量避免,太消費(fèi)性能,太費(fèi)時(shí)間,數(shù)組循環(huán)不推薦使用.
條件語句

常見的條件語句有if-elseswitch-case,那么什么時(shí)候用if-else,什么時(shí)候用switch-case語句呢?
  
我們先來看個(gè)簡(jiǎn)單的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次判斷才能返回正確的結(jié)果,那么我們?cè)趺磧?yōu)化這段代碼呢?一個(gè)顯而易見的優(yōu)化策略是將最可能的取值提前判斷,比如value最可能等于5或者10,那么將這兩條判斷提前。但是通常情況下我們并不知道(最可能的選擇),這時(shí)我們可以采取二叉樹查找策略進(jìn)行性能優(yōu)化。

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

這樣優(yōu)化后我們最多進(jìn)行4次判斷即可,大大提高了代碼的性能。這樣的優(yōu)化思想有點(diǎn)類似二分查找,和二分查找相似的是,只有value值是連續(xù)的數(shù)字時(shí)才能進(jìn)行這樣的優(yōu)化。但是代碼這樣寫的話不利于維護(hù),如果要增加一個(gè)條件,或者多個(gè)條件,就要重寫很多代碼,這時(shí)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語句讓代碼顯得可讀性更強(qiáng),而且swtich-case語句還有一個(gè)好處是如果多個(gè)value值返回同一個(gè)結(jié)果,就不用重寫return那部分的代碼。一般來說,當(dāng)case數(shù)達(dá)到一定數(shù)量時(shí),swtich-case語句的效率是比if-else高的,因?yàn)?b>switch-case采用了branch table(分支表)索引來進(jìn)行優(yōu)化,當(dāng)然各瀏覽器的優(yōu)化程度也不一樣。

相對(duì)來說,下面幾種情況更適合使用switch結(jié)構(gòu):

枚舉表達(dá)式的值。這種枚舉是可以期望的、平行邏輯關(guān)系的。

表達(dá)式的值具有離散性,不具有線性的非連續(xù)的區(qū)間值。

表達(dá)式的值是固定的,不是動(dòng)態(tài)變化的。

表達(dá)式的值是有限的,而不是無限的,一般情況下表達(dá)式應(yīng)該比較少。

表達(dá)式的值一般為整數(shù)、字符串等類型的數(shù)據(jù)。

而if結(jié)構(gòu)則更適合下面的一些情況:

具有復(fù)雜的邏輯關(guān)系。

表達(dá)式的值具有線性特征,如對(duì)連續(xù)的區(qū)間值進(jìn)行判斷。

表達(dá)式的值是動(dòng)態(tài)的。

測(cè)試任意類型的數(shù)據(jù)。

除了if-elseswtich-case外,我們還可以采用查找表。

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

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

  當(dāng)數(shù)據(jù)量很大的時(shí)候,查找表的效率通常要比if-else語句和swtich-case語句高,查找表能用數(shù)字和字符串作為索引,而如果是字符串的情況下,最好用對(duì)象來代替數(shù)組。當(dāng)然查找表的使用是有局限性的,每個(gè)case對(duì)應(yīng)的結(jié)果只能是一個(gè)取值而不能是一系列的操作。
  
  從根源上分析if elseswitch的效率,之前只知道if elseswitch算法實(shí)現(xiàn)不同,但具體怎么樣就不清楚了,為了刨根問底,翻閱了很多資料,但無奈找到了原因我還是不太懂,只知道就是這么回事,不懂匯編,懂底層匯編的大神還望多加指點(diǎn).
  
  想要深入從匯編的角度了解可以看看這篇文章:switch...case和if...else效率比較
  
  學(xué)前端的我想問你匯編是啥?能吃嗎?
  
    
  在選擇分支較多時(shí),選用switch...case結(jié)構(gòu)會(huì)提高程序的效率,但switch不足的地方在于只能處理字符或者數(shù)字類型的變量,if...else結(jié)構(gòu)更加靈活一些,if...else結(jié)構(gòu)可以用于判斷表達(dá)式是否成立,比如if(a+b>c),if...else的應(yīng)用范圍更廣,switch...case結(jié)構(gòu)在某些情況下可以替代if...else結(jié)構(gòu)。
  
小結(jié):

1. 當(dāng)只有兩個(gè)case或者case的value取值是一段連續(xù)的數(shù)字的時(shí)候,我們可以選擇if-else語句;
2. 當(dāng)有3~10個(gè)case數(shù)并且case的value取值非線性的時(shí)候,我們可以選擇switch-case語句;
3. 當(dāng)case數(shù)達(dá)到10個(gè)以上并且每次的結(jié)果只是一個(gè)取值而不是額外的JavaScript語句的時(shí)候,我們可以選擇查找表.
事件委托減少循環(huán)綁定的事件

什么是事件委托:通俗的講,事件就是onclick,onmouseover,onmouseout,等就是事件,委托呢,就是讓別人來做,這個(gè)事件本來是加在某些元素上的,然而你卻加到別人身上來做,完成這個(gè)事件。

也就是:利用冒泡的原理,把事件加到父級(jí)上,觸發(fā)執(zhí)行效果。
好處呢:
1.提高性能。
2.新添加的元素還會(huì)有之前的事件。

試想一下,一個(gè)頁面上ul的每一個(gè)li標(biāo)簽添加一個(gè)事件,我們會(huì)不會(huì)給每一個(gè)標(biāo)簽都添加一個(gè)onclick呢。 當(dāng)頁面中存在大量元素都需要綁定同一個(gè)事件處理的時(shí)候,這種情況可能會(huì)影響性能,不僅消耗了內(nèi)存,還多循環(huán)時(shí)間。每綁定一個(gè)事件都加重了頁面或者是運(yùn)行期間的負(fù)擔(dān)。對(duì)于一個(gè)富前端的應(yīng)用,交互重的頁面上,過多的綁定會(huì)占用過多內(nèi)存。 一個(gè)簡(jiǎn)單優(yōu)雅的方式就是事件委托。它是基于事件的工作流:逐層捕獲,到達(dá)目標(biāo),逐層冒泡。既然事件存在冒泡機(jī)制,那么我們可以通過給外層綁定事件,來處理所有的子元素出發(fā)的事件。
一個(gè)事件委托的簡(jiǎn)單實(shí)現(xiàn):

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

我們可以看一個(gè)例子:需要觸發(fā)每個(gè)li來改變他們的背景顏色。

  • 1
  • 2
  • 3

首先想到最直接的實(shí)現(xiàn):

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上面添加鼠標(biāo)事件。

但是如果說我們可能有很多個(gè)li用for循環(huán)的話就比較影響性能。

下面我們可以用事件委托的方式來實(shí)現(xiàn)這樣的效果。html不變

window.onload = () => {
    let oUl = document.querySelector("#ul");
    /*
    這里要用到事件源:event 對(duì)象,事件源,不管在哪個(gè)事件中,只要你操作的那個(gè)元素就是事件源。
    ie:window.event.srcElement
    標(biāo)準(zhǔn)下:event.target
    nodeName:找到元素的標(biāo)簽名
    */
    //雖然習(xí)慣用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,新添加的元素還會(huì)有之前的事件。

我們還拿這個(gè)例子看,但是我們要做動(dòng)態(tài)的添加li。點(diǎn)擊button動(dòng)態(tài)添加li


  • 1
  • 2
  • 3

不用事件委托我們會(huì)這樣做:

window.onload = () => {
    let oUl = document.querySelector("#ul");
    let aLi = oUl.querySelectorAll("li");
    let oBtn = document.querySelector("#btn");
    let iNow = 4;
    //剛才用forEach實(shí)現(xiàn),現(xiàn)在就用性能最高的for實(shí)現(xiàn),把剛才學(xué)的溫習(xí)一下

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

}

這樣做我們可以看到點(diǎn)擊按鈕新加的li上面沒有鼠標(biāo)移入事件來改變他們的背景顏色。

因?yàn)辄c(diǎn)擊添加的時(shí)候for循環(huán)已經(jīng)執(zhí)行完畢。

那么我們用事件委托的方式來做。就是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);
    }
}
更快速的數(shù)據(jù)訪問

對(duì)于瀏覽器來說,一個(gè)標(biāo)識(shí)符所處的位置越深,去讀寫他的速度也就越慢(對(duì)于這點(diǎn),原型鏈亦是如此)。這個(gè)應(yīng)該不難理解,簡(jiǎn)單比喻就是:雜貨店離你家越遠(yuǎn),你去打醬油所花的時(shí)間就越長(zhǎng)... 熊孩子,打個(gè)醬油那么久,菜早燒焦了 -.-~

我們?cè)诰幋a過程中多多少少會(huì)使用到一些全局變量(window,document,自定義全局變量等等),了解javascript作用域鏈的人都知道,在局部作用域中訪問全局變量需要一層一層遍歷整個(gè)作用域鏈直至頂級(jí)作用域,而局部變量的訪問效率則會(huì)更快更高,因此在局部作用域中高頻率使用一些全局對(duì)象時(shí)可以將其導(dǎo)入到局部作用域中,例如:
對(duì)比看看:

 //修改前
 function showLi(){
   var i = 0;
   for(;i

再來看看兩個(gè)簡(jiǎn)單的例子;

//1、作為參數(shù)傳入模塊  
 (function(window,$){  
     var xxx = window.xxx;  
     $("#xxx1").xxx();  
     $("#xxx2").xxx();  
 })(window,jQuery);  
   
 //2、暫存到局部變量  
 function(){  
    var doc = document;  
    var global = window.global;  
} 
eval以及類eval問題

我們都知道eval可以將一段字符串當(dāng)做js代碼來執(zhí)行處理,據(jù)說使用eval執(zhí)行的代碼比不使用eval的代碼慢100倍以上(具體效率我沒有測(cè)試,有興趣同學(xué)可以測(cè)試一下),前面的瀏覽器控制臺(tái)效率低下也提到eval這個(gè)問題.

JavaScript 代碼在執(zhí)行前會(huì)進(jìn)行類似“預(yù)編譯”的操作:首先會(huì)創(chuàng)建一個(gè)當(dāng)前執(zhí)行環(huán)境下的活動(dòng)對(duì)象,并將那些用 var 
申明的變量設(shè)置為活動(dòng)對(duì)象的屬性,但是此時(shí)這些變量的賦值都是 undefined,并將那些以 function 定義的函數(shù)也
添加為活動(dòng)對(duì)象的屬性,而且它們的值正是函數(shù)的定義。但是,如果你使用了“eval”,則“eval”中的代碼(實(shí)際上為字
符串)無法預(yù)先識(shí)別其上下文,無法被提前解析和優(yōu)化,即無法進(jìn)行預(yù)編譯的操作。所以,其性能也會(huì)大幅度降低

對(duì)上面js的預(yù)編譯,活動(dòng)對(duì)象等一些列不太明白的童鞋.看完這篇文章惡補(bǔ)一下,我想你會(huì)收獲很多:前端基礎(chǔ)進(jìn)階(三):變量對(duì)象詳解

其實(shí)現(xiàn)在大家一般都很少會(huì)用eval了,這里我想說的是兩個(gè)類eval的場(chǎng)景(new Function{},setTimeout,
setInterver)

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

上述幾種類型代碼執(zhí)行效率都會(huì)比較低,因此建議直接傳入匿名方法、或者方法的引用給setTimeout方法.

DOM操作的優(yōu)化

眾所周知的,DOM操作遠(yuǎn)比javascript的執(zhí)行耗性能,雖然我們避免不了對(duì)DOM進(jìn)行操作,但我們可以盡量去減少該操作對(duì)性能的消耗。

為什么操作DOM這么耗費(fèi)性能呢?

瀏覽器通常會(huì)把js和DOM分開來分別獨(dú)立實(shí)現(xiàn)。 
舉個(gè)栗子冷知識(shí),在IE中,js的實(shí)現(xiàn)名為JScript,位于jscript.dll文件中;DOM的實(shí)現(xiàn)則存在另一個(gè)庫中,名為mshtml.dll(Trident)。
Chrome中的DOM實(shí)現(xiàn)為webkit中的webCore,但js引擎是Google自己研發(fā)的V8。 
Firefox中的js引擎是SpiderMonkey,渲染引擎(DOM)則是Gecko。

DOM,天生就慢

前面的小知識(shí)中說過,瀏覽器把實(shí)現(xiàn)頁面渲染的部分和解析js的部分分開來實(shí)現(xiàn),既然是分開的,一旦兩者需要產(chǎn)生連接,就要付出代價(jià)。

兩個(gè)例子:

小明和小紅是兩個(gè)不同學(xué)校的學(xué)生,兩個(gè)人家里經(jīng)濟(jì)條件都不太好,買不起手機(jī)(好尷尬的設(shè)定Orz...),所以只能通過寫信來互相交流,這樣的過程肯定比他倆面對(duì)面交談時(shí)所需要花費(fèi)的代價(jià)大(額外的事件、寫信的成本等)。

官方例子:把DOM和js(ECMAScript)各自想象為一座島嶼,它們之間用收費(fèi)橋進(jìn)行連接。ECMAScript每次訪問DOM,都要途徑這座橋,并交納“過橋費(fèi)”。訪問DOM的次數(shù)越多,費(fèi)用也就越高。

因此,推薦的做法是:盡可能的減少過橋的次數(shù),努力待在ECMAScript島上。

讓我們通過一個(gè)最簡(jiǎn)單的代碼解釋這個(gè)問題:

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

針對(duì)以上方法進(jìn)行一次改寫:

function innerLi_s(){
   var content ="";
   var i = 0;
   for(;i<20;i++){
     content += "A"; //這里只對(duì)js的變量循環(huán)了20次
   };
   document.getElementById("Num").innerHTML += content; 
   //這里值進(jìn)行了一次DOM操作,又分2次DOM訪問:一次讀取innerHTML的值,一次寫入值
 };
減少頁面的重排(Reflows)和重繪(Repaints)

簡(jiǎn)單說下什么是重排和重繪:
瀏覽器下載完HTMl,CSS,JS后會(huì)生成兩棵樹:DOM樹和渲染樹。 當(dāng)Dom的幾何屬性發(fā)生變化時(shí),比如Dom的寬高,或者顏色,position,瀏覽器需要重新計(jì)算元素的幾何屬性,并且重新構(gòu)建渲染樹,這個(gè)過程稱之為重繪重排。

元素布局的改變或內(nèi)容的增刪改或者瀏覽器窗口尺寸改變都將會(huì)導(dǎo)致重排,而字體顏色或者背景色的修改則將導(dǎo)致重繪。
對(duì)于類似以下代碼的操作,據(jù)說現(xiàn)代瀏覽器大多進(jìn)行了優(yōu)化(將其優(yōu)化成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次重排版

針對(duì)多重操作,以下三種方法也可以減少重排版和重繪的次數(shù):

Dom先隱藏,操作后再顯示 2次重排 (臨時(shí)的display:none);

document.createDocumentFragment() 創(chuàng)建文檔片段處理,操作后追加到頁面 1次重排;

var newDOM = oldDOM.cloneNode(true)創(chuàng)建Dom副本,修改副本后oldDOM.parentNode.replaceChild
(newDOM,oldDOM)覆蓋原DOM 2次重排

如果是動(dòng)畫元素的話,最好使用絕對(duì)定位以讓它不在文檔流中,這樣的話改變它的位置不會(huì)引起頁面其它元素重排

更多DOM優(yōu)化的細(xì)節(jié)以及關(guān)于瀏覽器頁面的重排(Reflows)和重繪(Repaints)的概念和優(yōu)化請(qǐng)參考:天生就慢的DOM如何優(yōu)化?,花10+分鐘閱讀,你會(huì)受益匪淺.

盡量少去改變作用域鏈

使用with

try catch

我了解到的JavaScript中改變作用域鏈的方式只有兩種1)使用with表達(dá)式 2)通過捕獲異常try catch來實(shí)現(xiàn)

但是with是大家都深惡痛絕的影響性能的表達(dá)式,因?yàn)槲覀兺耆梢酝ㄟ^使用一個(gè)局部變量的方式來取代它(因?yàn)閣ith的原理是它的改變作用域鏈的同時(shí)需要保存很多信息以保證完成當(dāng)前操作后恢復(fù)之前的作用域鏈,這些就明顯的影響到了性能)

try catch中的catch子句同樣可以改變作用域鏈。當(dāng)try塊發(fā)生錯(cuò)誤時(shí),程序自動(dòng)轉(zhuǎn)入catch塊并將異常對(duì)象推入作用域鏈前端的一個(gè)可變對(duì)象中,也就是說在catch塊中,函數(shù)所有的局部變量已經(jīng)被放在第二個(gè)作用域鏈對(duì)象中,但是catch子句執(zhí)行完成之后,作用域鏈就會(huì)返回到原來的狀態(tài)。應(yīng)該最小化catch子句來保證代碼性能,如果知道錯(cuò)誤的概念很高,我們應(yīng)該盡量修正錯(cuò)誤而不是使用try catch.

最后

雖說現(xiàn)代瀏覽器都已經(jīng)做的很好了,但是本獸覺得這是自己對(duì)代碼質(zhì)量的一個(gè)追求。并且可能一個(gè)點(diǎn)或者兩個(gè)點(diǎn)不注意是不會(huì)產(chǎn)生多大性能影響,但是從多個(gè)點(diǎn)進(jìn)行優(yōu)化后,可能產(chǎn)生的就會(huì)質(zhì)的飛躍了
JavaScript 總結(jié)的這幾個(gè)提高性能知識(shí)點(diǎn),希望大家牢牢掌握。

參考文章:

高性能JavaScript循環(huán)語句和流程控制
if else 和 switch的效率
switch...case和if...else效率比較
JavaScript提高性能知識(shí)點(diǎn)匯總
JavaScript執(zhí)行效率小結(jié)
天生就慢的DOM如何優(yōu)化?
編寫高性能的JavaScript代碼

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/81742.html

相關(guān)文章

  • 大前端- 收藏集 - 掘金

    摘要:下面圍繞的這樣的目的,即左右知乎網(wǎng)頁上屏幕截圖功能的實(shí)現(xiàn)前端掘金背景最近注意到知乎的屏幕截圖反饋功能,感覺非常不錯(cuò)。正如你期望的,文中的闖關(guān)記之垃圾回收和內(nèi)存管理前端掘金題圖來源,授權(quán)基于協(xié)議。 微信小程序?qū)崙?zhàn)學(xué)習(xí) 起手式 DEMO 仿肯德基 - 前端 - 掘金小程序?大場(chǎng)景? 微信小程序本質(zhì)上來說就是一個(gè) HTML 5(移動(dòng)網(wǎng)頁) 應(yīng)用,用view、scoll-view代替了div標(biāo)...

    LdhAndroid 評(píng)論0 收藏0
  • 阿里巴巴高級(jí)技術(shù)專家至簡(jiǎn):聊工程師思維

    摘要:至簡(jiǎn)阿里巴巴高級(jí)技術(shù)專家,是集團(tuán)方向的重要參與者和推動(dòng)者。作者將工程師思維分解為產(chǎn)品技術(shù)和工程三大思維??焖俑霞夹g(shù)的迭代步伐是每個(gè)有追求的工程師不斷提升自己專業(yè)素養(yǎng)的表現(xiàn)之一。對(duì)于工程師來說,機(jī)制是通過系統(tǒng)性的軟件設(shè)計(jì)去達(dá)成的。 摘要: 為什么想到寫這篇文章?作者是想通過對(duì)工程師思維的分析和解讀,讓工程師能正確對(duì)待那些在現(xiàn)實(shí)工作中看上去與本職崗位無關(guān),卻對(duì)團(tuán)隊(duì)效能影響極大的一些點(diǎn)和一...

    cooxer 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<