从 V8 中看 JS 性能优化

🌙
手机阅读
本文目录结构

问题

从 V8 中看 JS 性能优化

注意:该知识点属于性能优化领域

1 测试性能⼯具

Chrome 已经提供了⼀个⼤⽽全的性能测试⼯具 Audits

点我们点击 Audits 后,可以看到如下的界⾯

在这个界⾯中,我们可以选择想测试的功能然后点击 Run audits ,⼯具就 会⾃动运⾏帮助我们测试问题并且给出⼀个完整的报告

上图是给掘⾦⾸⻚测试性能后给出的⼀个报告,可以看到报告中分别为性能、 体验、SEO 都给出了打分,并且每⼀个指标都有详细的评估

评估结束后,⼯具还提供了⼀些建议便于我们提⾼这个指标的分数

我们只需要⼀条条根据建议去优化性能即可。

除了 Audits ⼯具之外,还有⼀个 Performance ⼯具也可以供我们使⽤。 在这张图中,我们可以详细的看到每个时间段中浏览器在处理什么事情,哪个 过程最消耗时间,便于我们更加详细的了解性能瓶颈

2 JS 性能优化

JS 是编译型还是解释型语⾔其实并不固定。⾸先 JS 需要有引擎才能运⾏ 起来,⽆论是浏览器还是在 Node 中,这是解释型语⾔的特性。但是在 V8 引擎下,⼜引⼊了 TurboFan 编译器,他会在特定的情况下进⾏优化,将代 码编译成执⾏效率更⾼的 Machine Code ,当然这个编译器并不是 JS 必须 需要的,只是为了提⾼代码执⾏性能,所以总的来说 JS 更偏向于解释型语 ⾔。

那么这⼀⼩节的内容主要会针对于 Chrome 的 V8 引擎来讲解。

在这⼀过程中, JS 代码⾸先会解析为抽象语法树( AST ),然后会通过解 释器或者编译器转化为 Bytecode 或者 Machine Code

从上图中我们可以发现, JS 会⾸先被解析为 AST ,解析的过程其实是略慢 的。代码越多,解析的过程也就耗费越⻓,这也是我们需要压缩代码的原因之 ⼀。另外⼀种减少解析时间的⽅式是预解析,会作⽤于未执⾏的函数,这个我 们下⾯再谈

这⾥需要注意⼀点,对于函数来说,应该尽可能避免声明嵌套函数(类也是函数),因为这样 会造成函数的重复解析

function test1() {
    // 会被重复解析
    function test2() {}
}

然后 Ignition 负责将 AST 转化为 Bytecode , TurboFan 负责编译出 优化后的 Machine Code ,并且 Machine Code 在执⾏效率上优于 Bytecode

那么我们就产⽣了⼀个疑问,什么情况下代码会编译为 Machine Code ?

JS 是⼀⻔动态类型的语⾔,并且还有⼀⼤堆的规则。简单的加法运算代码, 内部就需要考虑好⼏种规则,⽐如数字相加、字符串相加、对象和字符串相加 等等。这样的情况也就势必导致了内部要增加很多判断逻辑,降低运⾏效率。

function test1() {
// 会被重复解析
function test2() {}
}
function test(x) {
    return x + x
}
test(3)
test(4)
test(1)
test(2)

对于以上代码来说,如果⼀个函数被多次调⽤并且参数⼀直传⼊ number 类型,那么 V8 就会认为该段代码可以编译为 Machine Code ,因为你固定了类型,不需要再执⾏ 很多判断逻辑了。

但是如果⼀旦我们传⼊的参数类型改变,那么 Machine Code 就会被 DeOptimized 为 Bytecode ,这样就有性能上的⼀个损耗了。所以如果我们希望代码能多的编译为 Machine Code 并且 DeOptimized 的次数减少,就应该尽可能保证传⼊的类型⼀致。 那么你可能会有⼀个疑问,到底优化前后有多少的提升呢,接下来我们就来实践测试⼀下 到底有多少的提升

const { performance, PerformanceObserver } = require('perf_hooks')
function test(x) {
    return x + x
}
// node 10 中才有 PerformanceObserver
// 在这之前的 node 版本可以直接使⽤ performance 中的 API
const obs = new PerformanceObserver((list, observer) => {
    console.log(list.getEntries())
    observer.disconnect()
})
obs.observe({ entryTypes: ['measure'], buffered: true })
performance.mark('start')
let number = 10000000
// 不优化代码
%NeverOptimizeFunction(test)
while (number--) {
    test(1)
}
performance.mark('end')
performance.measure('test', 'start', 'end')

以上代码中我们使⽤了 performance API ,这个 API 在性能测试上⼗分好 ⽤。不仅可以⽤来测量代码的执⾏时间,还能⽤来测量各种⽹络连接中的时间 消耗等等,并且这个 API 也可以在浏览器中使

从上图中我们可以发现,优化过的代码执⾏时间只需要 9ms ,但是不优化过 的代码执⾏时间却是前者的⼆⼗倍,已经接近 200ms 了。在这个案例中,我 相信⼤家已经看到了 V8 的性能优化到底有多强,只需要我们符合⼀定的规 则书写代码,引擎底层就能帮助我们⾃动优化代码。

另外,编译器还有个骚操作 Lazy-Compile ,当函数没有被执⾏的时候,会 对函数进⾏⼀次预解析,直到代码被执⾏以后才会被解析编译。对于上述代码 来说, test 函数需要被预解析⼀次,然后在调⽤的时候再被解析编译。但是 对于这种函数⻢上就被调⽤的情况来说,预解析这个过程其实是多余的,那么 有什么办法能够让代码不被预解析呢?

(function test(obj) {
    return x + x
})

但是不可能我们为了性能优化,给所有的函数都去套上括号,并且也不是所有 函数都需要这样做。我们可以通过 optimize-js 实现这个功能,这个库会分 析⼀些函数的使⽤情况,然后给需要的函数添加括号,当然这个库很久没⼈维 护了,如果需要使⽤的话,还是需要测试过相关内容的。

其实很简单,我们只需要给函数套上括号就可以了

更多面试题

如果你想了解更多的前端面试题,可以查看本站的WEB前端面试题 ,这里基本包涵了市场上的所有前端方面的面试题,也有一些大公司的面试图,可以让你面试更加顺利。

面试题
HTML CSS JavaScript
jQuery Vue.js React
算法 HTTP Babel
BootStrap Electron Gulp
Node.js 前端经验相关 前端综合
Webpack 微信小程序 -

这些题库还在更新中,如果你有不错的面试题库欢迎分享给我,我整理后放上来;人人为我,我为人人,互帮互助,共同提高,祝大家都拿到心仪的Offer!

AXIHE / 精选资源

浏览全部教程

面试题

学习网站

前端培训
自己甄别

前端书籍

关于朱安邦

我叫 朱安邦,阿西河的站长,在杭州。

以前是一名平面设计师,后来开始接接触前端开发,主要研究前端技术中的JS方向。

业余时间我喜欢分享和交流自己的技术,欢迎大家关注我的 Bilibili

关注我: Github / 知乎

于2021年离开前端领域,目前重心放在研究区块链上面了

我叫朱安邦,阿西河的站长

目前在杭州从事区块链周边的开发工作,机械专业,以前从事平面设计工作。

2014年底脱产在老家自学6个月的前端技术,自学期间几乎从未出过家门,最终找到了满意的前端工作。更多>

于2021年离开前端领域,目前从事区块链方面工作了