阿西河

所有教程

公众号
🌙
阿西河前端的公众号

我的收藏

    最近访问  (文章)

      教程列表

      抓包专区
      测试专区

      从 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前端面试题 ,这里基本包涵了市场上的所有前端方面的面试题,也有一些大公司的面试图,可以让你面试更加顺利。

      面试题
      HTMLCSSJavaScript
      jQueryVue.jsReact
      算法HTTPBabel
      BootStrapElectronGulp
      Node.js前端经验相关前端综合
      Webpack微信小程序-

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

      目录
      目录