這幾天分享一下我看《高性能 JavaScript》的學(xué)習(xí)筆記胀莹,希望能對(duì)大家有所幫助。
循環(huán)
在 JavaScript中一共有以下幾種循環(huán):
- while(i < len) { ... }
- do { ... } while(i < len)
- for (let i = 0; i < len; i++) { ... }
- for (let key in obj) { ... }
- 還有 ES6 中添加的 for (let value of obj) { ... }
這些循環(huán)方法中蒲障,前三種循環(huán)方式性能最佳。后面兩種循環(huán)需要搜索對(duì)象實(shí)例和原型瘫证,所以必有更多的性能開銷揉阎。
如何提高循環(huán)性能
需要循環(huán)性能的好壞主要看兩個(gè)點(diǎn):
- 每次迭代要處理的事務(wù)復(fù)雜度。
- 迭代的次數(shù)背捌。
那么有哪些優(yōu)化循環(huán)的方法呢毙籽?
- 限制循環(huán)迭代中耗時(shí)操作的數(shù)量。
- 減少對(duì)象成員及數(shù)組的查找次數(shù)毡庆,如 obj.name 或者 arr.length 坑赡。可以使用局部變量進(jìn)行保存么抗。
- 倒序循環(huán)的效率比正序循環(huán)稍高垮衷。
減少迭代次數(shù)
有一種叫達(dá)夫設(shè)備的優(yōu)化方案,它主要思想是通過展開循環(huán)體來減少迭代的次數(shù)乖坠。
var iterations = Math.floor(items.length / 8),
startAt = items.length % 8,
i = 0;
do {
switch (startAt) {
case 0: precess(items[i++]);
case 7: precess(items[i++]);
case 6: precess(items[i++]);
case 5: precess(items[i++]);
case 4: precess(items[i++]);
case 3: precess(items[i++]);
case 2: precess(items[i++]);
case 1: precess(items[i++]);
}
startAt = 0
} while (--iterations);
forEach 循環(huán)
數(shù)組提供了 forEach() 函數(shù)讓我們可以遍歷數(shù)組內(nèi)容搀突。
雖然 forEach() 函數(shù)是更為便利的迭代方法,因?yàn)閿?shù)組項(xiàng)調(diào)用外部方法回來帶開銷熊泵⊙銮ǎ基于循環(huán)的迭代比基于函數(shù)的迭代快 8 倍。
條件
在 JavaScript 中顽分,主要的條件判斷方式有兩種:
- if-else
- switch
條件數(shù)量越大徐许,越是傾向于使用 switch 來判斷。因?yàn)?switch 在大量判斷數(shù)據(jù)下易讀性更好卒蘸,而且在大多數(shù)情況下大數(shù)據(jù)量的判斷 switch 的性能會(huì)更佳雌隅。
如果必須用 if-else 判斷有規(guī)律的數(shù)據(jù),可以使用二分法減少查詢條件的次數(shù):
if (value < 6) {
if (value < 3) {
if (value == 0) {
return 'value is 0'
} else if (value == 1) {
return 'value is 1'
} else {
return 'value is 2'
}
} else {
if (value == 3) {
return 'value is 3'
} else if (value == 4) {
return 'value is 4'
} else {
return 'value is 5'
}
}
} else {
if (value < 8) {
if (value == 6) {
return 'value is 6'
} else {
return 'value is 7'
}
} else {
if (value == 8) {
return 'value is 8'
} else if (value == 9) {
return 'value is 9'
} else {
return 'value is 10'
}
}
}
書中還提出了一種面對(duì)大量離散值時(shí)的優(yōu)化方案 —— 通過數(shù)組或?qū)ο髽?gòu)建查找表缸沃,然后從數(shù)組或?qū)ο笾胁檎覕?shù)據(jù)結(jié)果恰起。
雖然查找數(shù)組和對(duì)象的屬性需要一定性能開銷,但是比條件判斷的速度要快趾牧。而且可讀性也更好检盼。
// switch 寫法
function switchFunc(value) {
switch (value) {
case 0: return 'value is 0';
case 1: return 'value is 1';
case 2: return 'value is 2';
case 3: return 'value is 3';
case 4: return 'value is 4';
case 5: return 'value is 5';
case 6: return 'value is 6';
case 7: return 'value is 7';
case 8: return 'value is 8';
case 9: return 'value is 9';
case 10: return 'value is 10';
}
}
// 查找表寫法
function searchFunc(value) {
var results = [
'value is 0',
'value is 1',
'value is 2',
'value is 3',
'value is 4',
'value is 5',
'value is 6',
'value is 7',
'value is 8',
'value is 9',
'value is 10'
]
return results[value]
}
遞歸
遞歸就是在函數(shù)中直接或間接調(diào)用函數(shù)自身的行為。一般遞歸都會(huì)有一個(gè)停止條件的判斷翘单,滿足停止條件后停止遞歸行為吨枉。遞歸有兩種模式:
- 直接遞歸模式
function recurse() {
recurse();
}
recurse();
- 隱伏模式
function first() {
second();
}
function second() {
first();
}
first();
然而蹦渣,如果遞歸次數(shù)過多過快,會(huì)觸發(fā)調(diào)用棧限制錯(cuò)誤貌亭,幸好這種錯(cuò)誤可以通過 try ... catch
來捕獲柬唯。
try {
recurse()
} catch (ex) {
alert("too much recursion")
}
其實(shí),使用遞歸并非是最好的選擇圃庭。書上提到運(yùn)行一個(gè)循環(huán)的速度遠(yuǎn)快于調(diào)用函數(shù)自身(遞歸)权逗。所以如果可以用循環(huán)來實(shí)現(xiàn)的邏輯盡量不要用遞歸來做。
Memoization 技術(shù)
眾所周知冤议,代碼處理的邏輯越少斟薇,運(yùn)行速度必然越快。所以我們應(yīng)該避免做一些重復(fù)的工作恕酸。做法就是通過對(duì)象將一些重復(fù)工作的行為結(jié)果緩存下來堪滨,第一次之后的工作直接使用緩存結(jié)果。
function memoize(fundamental, cache) {
cache = cache || {};
var shell = function(arg) {
if (!cache.hasOwnProperty(arg)) {
cache[arg] = fundamental
}
return cache[arg]
}
return shell;
}
這里將添加的行為存到 cache 對(duì)象中來避免重復(fù)工作蕊温,這樣做是可以提升性能的袱箱。
小結(jié)
本文主要說了一些 JavaScript 循環(huán)、條件义矛、遍歷发笔、遞歸等算法的優(yōu)化思路。這些知識(shí)點(diǎn)在實(shí)現(xiàn)復(fù)雜算法的時(shí)候是很有用的凉翻。下面簡(jiǎn)單整理下~
- 不同的算法在數(shù)據(jù)量大的情況下性能差別很大了讨。
- 遍歷行為要盡量減少迭代次數(shù)、減少行為的耗時(shí)操作制轰∏凹疲可以使用緩存對(duì)象來避免一些重復(fù)行為的發(fā)生。
- 遞歸時(shí)要注意 JavaScript 有調(diào)用棧限制垃杖。
- 查找表技術(shù)很適合大量離散表的條件判斷查找工作男杈。
對(duì)于算法和流程控制這方面,建議多學(xué)習(xí)一些算法知識(shí)调俘。這樣在真正面對(duì)復(fù)雜流程時(shí)可以知道有哪些算法可以提升性能了伶棒。