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

这几天分享一下我看《高性能 JavaScript》的学习笔记,希望能对大家有所帮助。

循环

在 JavaScript中一共有以下几种循环:

  • 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) { ... }

这些循环方法中,前三种循环方式性能最佳。后面两种循环需要搜索对象实例和原型,所以必有更多的性能开销。

如何提高循环性能

需要循环性能的好坏主要看两个点:

  1. 每次迭代要处理的事务复杂度。
  2. 迭代的次数。

那么有哪些优化循环的方法呢?

  • 限制循环迭代中耗时操作的数量。
  • 减少对象成员及数组的查找次数,如 obj.name 或者 arr.length 。可以使用局部变量进行保存。
  • 倒序循环的效率比正序循环稍高。

减少迭代次数

有一种叫达夫设备的优化方案,它主要思想是通过展开循环体来减少迭代的次数。

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 循环

数组提供了 forEach() 函数让我们可以遍历数组内容。

虽然 forEach() 函数是更为便利的迭代方法,因为数组项调用外部方法回来带开销。基于循环的迭代比基于函数的迭代快 8 倍。

条件

在 JavaScript 中,主要的条件判断方式有两种:

  • if-else
  • switch

条件数量越大,越是倾向于使用 switch 来判断。因为 switch 在大量判断数据下易读性更好,而且在大多数情况下大数据量的判断 switch 的性能会更佳。

如果必须用 if-else 判断有规律的数据,可以使用二分法减少查询条件的次数:

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

书中还提出了一种面对大量离散值时的优化方案 —— 通过数组或对象构建查找表,然后从数组或对象中查找数据结果。

虽然查找数组和对象的属性需要一定性能开销,但是比条件判断的速度要快。而且可读性也更好。

// 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]
}

递归

递归就是在函数中直接或间接调用函数自身的行为。一般递归都会有一个停止条件的判断,满足停止条件后停止递归行为。递归有两种模式:

  • 直接递归模式
function recurse() {
  recurse();
}

recurse();
  • 隐伏模式
function first() {
  second();
}

function second() {
  first();
}

first();

然而,如果递归次数过多过快,会触发调用栈限制错误,幸好这种错误可以通过 try ... catch 来捕获。

try {
  recurse()
} catch (ex) {
  alert("too much recursion")
}

其实,使用递归并非是最好的选择。书上提到运行一个循环的速度远快于调用函数自身(递归)。所以如果可以用循环来实现的逻辑尽量不要用递归来做。

Memoization 技术

众所周知,代码处理的逻辑越少,运行速度必然越快。所以我们应该避免做一些重复的工作。做法就是通过对象将一些重复工作的行为结果缓存下来,第一次之后的工作直接使用缓存结果。

function memoize(fundamental, cache) {
    cache = cache || {};

    var shell = function(arg) {
        if (!cache.hasOwnProperty(arg)) {
            cache[arg] = fundamental
        }
        return cache[arg]
    }

    return shell;
}

这里将添加的行为存到 cache 对象中来避免重复工作,这样做是可以提升性能的。

小结

本文主要说了一些 JavaScript 循环、条件、遍历、递归等算法的优化思路。这些知识点在实现复杂算法的时候是很有用的。下面简单整理下~

  • 不同的算法在数据量大的情况下性能差别很大。
  • 遍历行为要尽量减少迭代次数、减少行为的耗时操作。可以使用缓存对象来避免一些重复行为的发生。
  • 递归时要注意 JavaScript 有调用栈限制。
  • 查找表技术很适合大量离散表的条件判断查找工作。

对于算法和流程控制这方面,建议多学习一些算法知识。这样在真正面对复杂流程时可以知道有哪些算法可以提升性能了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容