微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

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

2016-12-01 21:14 前端大全

(点击上方公众号,可快速关注)


作者:Stark伟

链接:zhuanlan.zhihu.com/p/23629542



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

(function(){/* 函数体 */})()

 

!function(){/* 函数体 */}()


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

(function(global, factory) {

    //......

})(typeof window !== "undefined" ? window : this, function(window, noGlobal) {

    //......

})


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

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

// 方法一,传入一个匿名函数

function run(f){

    return f();

}

run(function(){

    //......

});

 

// 方法二,使用IIFE

(function(){

    //......

})()


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

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

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

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

// 下面的函数会先进行pre-parse

function foo() {

    //......

}

 

// 2秒之后函数被执行,又会进行一遍full-parse

setTimeout(foo, 2000);


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

// 只会进行一次full-parse

(function() {

   //......

})();


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

// 这里要进行pre-parse和full-parse,而前者是多余的

function run(f){

    return f();

}

run(function(){

    //......

})


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

// 只进行一次full-parse

function run(f){

    return f();

}

run((function(){

    //......

}));


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

nolanlawson/optimize-js: Optimize a JavaScript file for faster initial load by wrapping eagerly-invoked functions(https://github.com/nolanlawson/optimize-js)

还有一个相关的讨论:

Turn off negate_iife by default as it hurts V8 performance. · Issue #886 · mishoo/UglifyJS2(https://github.com/mishoo/UglifyJS2/issues/886)

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

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


关注「前端大全」

看更多精选前端技术文章

↓↓↓

 
前端大全 更多文章 漫画:脚本自动化的理想和现实 3种 web 会话管理的方式 一道 JS 面试题引发的思考 一道 JS 面试题引发的思考 了解真实的『REM』手机屏幕适配
猜您喜欢 嫌疑人X的献身 R语言中常用的数学函数及简短的用例 Swift语言将会对IT行业未来产生什么影响? 性能测试应该怎么做? iOS 9.2 为中移动用户带来 语音信箱 新功能