【DailyENJS第12期】V8引擎和JavaScript优化技巧

DailyENJS 致力于翻译优秀的前端英文技术文章,为技术同窗带来更好的技术视野。前端

V8是Google用来编译JavaScript的引擎。Firefox拥有本身的名为SpiderMonkey的引擎,与V8十分类似,但有所不一样。咱们将在本文中讨论V8引擎。git

有关V8引擎的一些事实:github

  • 用C ++编写,并在Chrome和Node.js(以及Microsoft Edge的最新版本)中使用
  • 实现 ECMA-262 标准

那么,当咱们发送要由V8引擎解析的JavaScript时(在将其缩小,丑化以后以及您对JavaScript代码进行的其余疯狂处理以后),到底发生了什么?web

我画了下面这个图,图里显示了全部步骤,而后咱们将详细讨论每一个步骤:浏览器

在本文中,咱们将讨论JavaScript代码是如何解析的,以及如何尽量地让你的 JavaScript 走到 Optimising Compiler。Optimizing Compiler(又名Turbofan)将咱们的JavaScript代码转换为高性能机器代码,所以,咱们能够给它提供的代码越多,咱们的应用程序就会越快。附带说明一下,Chrome中的解释器称为 Ignition。app

解析JavaScript

所以,咱们的JavaScript代码第一步是对其进行解析。让咱们讨论一下究竟是什么解析。ide

解析分为两种方式:函数

  • 非惰性(彻底解析)-这会当即解析每行
  • 懒惰(预先解析)-进行最少的工做,解析咱们须要的内容,其他部分留到之后

哪一个更好?性能

让咱们看一些代码。测试

// 当即解析
const a = 1;
const b = 2;

// 惰性解析,由于暂时用不到
function add(a, b) {
  return a + b;
}

// 由于使用到了,因此返回去 解析
add(a, b);
复制代码

在这里变量声明将被当即解析了,可是咱们的函数将被延迟解析。在咱们添加 add(a,b) 以前,这是很棒的,可是由于咱们须要使用这个函数,所以当即解析 add 会更快。

为了当即解析 add 函数,咱们能够执行如下操做:

// 当即解析
const a = 1;
const b = 2;

// 当即解析
var add = (function(a, b) {
  return a + b;
})();

// 当咱们使用到这个函数的时候已经被解析了
add(a, b);
复制代码

这就是你使用的大多数 module 的建立方式。那么,有能够解析出性能最佳的JavaScript应用程序的最佳方法吗?

让咱们看一下库 optimize-js,它遍历库并当即解析全部代码。若是咱们看看像lodash这样的流行库,这些优化是很棒的:

  • Without optimize-js: 11.86ms
  • With optimize-js: 11.24ms

可是必须考虑到,这是在Chrome浏览器环境中,在其余环境中,可能会有所不一样:

所以,对您的web应用进行这些优化时,重要的是要在将要运行应用的全部环境中进行测试。

另外一个解析技巧是不要将函数嵌套在其余函数中:

// bad way
function sumOfSquares(a, b) {
  // this is lazily parsed over and over
  function square(num) {
    return num * num;
  }

  return square(a) + square(b);
}
复制代码

更好的方法是:

function square(num) {
  return num * num;
}

// good way
function sumOfSquares(a, b) {
  return square(a) + square(b);
}

sumOfSquares(a, b);
复制代码

在上面状况下,square 仅被延迟解析一次。

函数内联

Chrome有时实际上会重写您的JavaScript,其中一个示例是内联正在使用的函数。

让咱们以如下代码为例:

const square = (x) => { return x * x }

const callFunction100Times = (func) => {
  for(let i = 100; i < 100; i++) {
    // the func param will be called 100 times
    func(2)
  }
}

callFunction100Times(square)
复制代码

上面的代码将经过V8引擎进行优化,以下所示:

const square = (x) => { return x * x }

const callFunction100Times = (func) => {
  for(let i = 100; i < 100; i++) {
    // the function is inlined so we don't have
    // to keep calling func
    return x * x
  }
}

callFunction100Times(square)
复制代码

从上面能够看到,V8本质上删除了咱们调用func的步骤,而是内联了 square 的内通。这很是有用,由于它将提升咱们的代码的性能。

函数内联陷阱

这种方法有些陷阱,让咱们看下面的代码示例:

const square = (x) => { return x * x }
const cube = (x) => { return x * x * x }

const callFunction100Times = (func) => {
  for(let i = 100; i < 100; i++) {
    // the function is inlined so we don't have
    // to keep calling func
    func(2)
  }
}

callFunction100Times(square)
callFunction100Times(cube)

复制代码

所以,此次咱们调用 square 函数100次以后,而后调用 cube 函数100次。在调用 cube 以前,咱们必须首先对callFunction100Times 进行优化,由于咱们已内嵌了 square 函数主题。在这种状况下,square 函数彷佛比 cube 函数快,可是事实是优化步骤使执行时间更长。

Objects

当涉及对象时,V8底层有用于区分你的的对象的类型系统:

Monomorphism

对象具备相同的键:

// mono example
const person = { name: 'John' }
const person2 = { name: 'Paul' }
复制代码

Polymorphism

这些对象共享类似的结构,但有一些细微差异。

// poly example
const person = { name: 'John' }
const person2 = { name: 'Paul', age: 27 }
复制代码

Megamorphism

对象彻底不一样,没法比较。

// mega example
const person = { name: 'John' }
const building = { rooms: ['cafe', 'meeting room A', 'meeting room B'], doors: 27 }
复制代码

如今,咱们了解了V8中的不一样对象,让咱们看看V8如何优化咱们的对象。

隐藏类(Hidden classes)

隐藏类是V8识别咱们的对象的方式。

让咱们将其分解为几个步骤。

咱们声明一个对象:

const obj = { name: 'John'}
复制代码

而后,V8将为此对象声明一个classId。

const objClassId = ['name', 1]
复制代码

而后,咱们的对象建立以下:

const obj = {...objClassId, 'John'}
复制代码

而后,当咱们像这样访问对象的name属性时:

obj.name
复制代码

V8执行如下查询:

obj[getProp(obj[0], name)]
复制代码

V8是建立对象时经历的过程,如今让咱们看看如何优化对象并重用classId。

建立对象的技巧

若是能够,则应在构造函数中声明属性。这将确保对象结构保持不变,以便V8而后能够优化您的对象。

class Point {
  constructor(x,y) {
    this.x = x
    this.y = y
  }
}

const p1 = new Point(11, 22) // hidden classId created
const p2 = new Point(33, 44)
复制代码

你应该使属性顺序保持不变,请使用如下示例:

const obj = { a: 1 } // hidden class created
obj.b = 3

const obj2 = { b: 3 } // another hidden class created
obj2.a = 1

// this would be better
const obj = { a: 1 } // hidden class created
obj.b = 3

const obj2 = { a: 1 } // hidden class is reused
obj2.b = 3
复制代码

常规优化技巧

如今,让咱们进入一些通用技巧,这些技巧将帮助您更好地优化JavaScript代码。

修复函数参数类型

将参数传递给函数时,重要的是它们必须是同一类型。若是参数类型不一样,Turbofan将在尝试4次后放弃尝试优化JavaScript。

function add(x,y) {
  return x + y
}

add(1,2) // monomorphic
add('a', 'b') // polymorphic
add(true, false)
add({},{})
add([],[]) // megamorphic - at this stage, 4+ tries, no optimization will happen
复制代码

另外一个技巧是确保在全局做用域内声明类:

// don't do this
function createPoint(x, y) {
  class Point {
    constructor(x,y) {
      this.x = x
      this.y = y
    }
  }

  // new point object created every time
  return new Point(x,y)
}

function length(point) {
  //...
}
复制代码

结论

我但愿你了解了V8的工做原理以及如何编写更好的优化JavaScript代码的一些知识。

原文: alligator.io/js/v8-engin…

最后照旧是一个广告贴,最近新开了一个分享技术的公众号,欢迎你们关注👇(目前关注人数可怜🤕)

相关文章
相关标签/搜索