高性能 JavaScript - 算法和流程控制

這幾天分享一下我看《高性能 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):

  1. 每次迭代要處理的事務(wù)復(fù)雜度。
  2. 迭代的次數(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í)可以知道有哪些算法可以提升性能了伶棒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彩库,隨后出現(xiàn)的幾起案子肤无,更是在濱河造成了極大的恐慌,老刑警劉巖侧巨,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舅锄,死亡現(xiàn)場(chǎng)離奇詭異鞭达,居然都是意外死亡司忱,警方通過查閱死者的電腦和手機(jī)皇忿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坦仍,“玉大人鳍烁,你說我怎么就攤上這事》痹” “怎么了幔荒?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長梳玫。 經(jīng)常有香客問我爹梁,道長,這世上最難降的妖魔是什么提澎? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任姚垃,我火速辦了婚禮,結(jié)果婚禮上盼忌,老公的妹妹穿的比我還像新娘积糯。我一直安慰自己,他們只是感情好谦纱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布看成。 她就那樣靜靜地躺著,像睡著了一般跨嘉。 火紅的嫁衣襯著肌膚如雪川慌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天祠乃,我揣著相機(jī)與錄音窘游,去河邊找鬼。 笑死跳纳,一個(gè)胖子當(dāng)著我的面吹牛忍饰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寺庄,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼艾蓝,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了斗塘?” 一聲冷哼從身側(cè)響起赢织,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馍盟,沒想到半個(gè)月后于置,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贞岭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年八毯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搓侄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡话速,死狀恐怖讶踪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泊交,我是刑警寧澤乳讥,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站廓俭,受9級(jí)特大地震影響云石,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜研乒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一留晚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧告嘲,春花似錦错维、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仰楚,卻和暖如春隆判,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背僧界。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工侨嘀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捂襟。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓咬腕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親葬荷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涨共,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容