合理使用IIFE优化JS引擎的性能

v2-e8c0580b5fb373973e0536117a617341_b

说起立即执行函数(IIFE,Immediately-invoked function expression)大家应该都不陌生,在 JavaScript 中可以声明一个函数然后立即执行它:

IIFE 通常用于实现私有变量、实现独立模块等等地方,比如喜闻乐见的 jQuery 最顶层的结构就是这样的:

但我们今天要说的不是 IIFE 怎么用,而是关于它针对 JS 引擎的一处性能优化。

先从一个小问题说起吧,想实现一个立即执行的函数,我们有很多种写法,比如下面这两种:

是的,这两种写法完全是等价的,无论怎么看都不会有什么区别,但是在一些 JavaScript 引擎中,它们其实性能相差甚远。

我使用 Node 分别对两种情况运行了十万次,方法一的运行时间平均在360毫秒左右,方法二平均是 50 毫秒左右,性能相差7倍还多。

为什么使用 IIFE 之后性能会提升那么多呢?这就要从 JS 引擎(比如V8、SpiderMonkey)对于函数的优化上说起了。

现在的 JS 引擎都是十分聪明的,它们在真正执行代码之前会对代码中函数声明做一遍 pre-parse(预解析),为啥要做 pre-parse 呢?因为实际情况中大多数的函数都不是立即被使用的(甚至完全没被调用过),不需要对它们做一次完整的解析,只需要做一次性能开销更小的 pre-parse(比如检查一下语法错误),等函数真正被调用时,再进行完整的 full-parse。

但是有个问题!对于立即执行函数这种奇葩来说,它不适用于上面的规则,应该直接进行 full-parse。现在的大多数引擎也完全考虑到了这一点:

但是还有个问题!!现在的大多数引擎检测 IIFE 的时候都不完全,大部分都是通过判别函数声明前有没有类似『 ( 』或者『 ! 』这样的字符来实现的,比如下面这种情况就被忽略掉了:

所以我们可以通过一个小 trick 来优化这里的性能(加了一对括号),这样引擎就会把这里识别为立即执行函数,然后只做一次 full-parse:

所以针对这个问题,有一个专门的小工具来解决:

nolanlawson/optimize-js: Optimize a JavaScript file for faster initial load by wrapping eagerly-invoked functions

还有一个相关的讨论:

Turn off negate_iife by default as it hurts V8 performance. · Issue #886 · mishoo/UglifyJS2

这个看似不起眼的 trick 实际对于性能有很显著的提升:

v2-1550a61ad1198349e328131e04981f21_b

这个问题本质上来讲是 JS 引擎对于立即执行函数的识别有遗漏导致的,比如在 Safari 10 中这个问题基本不会发生,而 Chrome 的 V8 中就经常出现。不过感觉随着引擎版本的迭代,这个问题应该会得到修复。

1 1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部