摘要:這樣優(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ù)綁定主要在getter和setter函數(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-else和switch-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-else和swtich-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 else 和 switch的效率,之前只知道if else 和switch算法實(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
不用事件委托我們會(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
摘要:下面圍繞的這樣的目的,即左右知乎網(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)...
摘要:至簡(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)和一...
閱讀 2435·2021-09-01 10:41
閱讀 1451·2019-08-30 14:12
閱讀 520·2019-08-29 12:32
閱讀 2868·2019-08-29 12:25
閱讀 2943·2019-08-28 18:30
閱讀 1713·2019-08-26 11:47
閱讀 989·2019-08-26 10:35
閱讀 2597·2019-08-23 18:06