V8引擎和JavaScript优化技巧

来自菜鸟教程
跳转至:导航、​搜索

V8 是 Google 用于编译 JavaScript 的引擎。 Firefox 有自己的引擎 SpiderMonkey,它与 V8 非常相似,但也有区别。 我们将在本文中讨论 V8 引擎。

关于 V8 发动机的几个事实:

JavaScript 之旅

那么,当我们将 JavaScript 发送给 V8 引擎解析时(这是在它被缩小、丑化和你对 JavaScript 代码做的任何其他疯狂的事情之后)究竟会发生什么?

我创建了下图,显示了所有步骤,然后我们将详细讨论每个步骤:

在本文中,我们将讨论如何解析 JavaScript 代码以及如何将尽可能多的 JavaScript 获取到优化编译器。 优化编译器(又名 Turbofan)获取我们的 JavaScript 代码并将其转换为高性能机器代码,因此我们可以提供的代码越多,我们的应用程序就越快。 附带说明一下,Chrome 中的解释器称为 Ignition.

解析 JavaScript

所以我们的 JavaScript 代码的第一个处理就是解析它。 让我们来讨论一下什么是解析。

解析有两个阶段:

  • Eager (full-parse) - 这会立即解析每一行
  • Lazy (pre-parse)- 做最少的事情,解析我们需要的东西,剩下的留到以后

哪个更好? 这一切都取决于。

让我们看一些代码。

// eager parse declarations right away
const a = 1;
const b = 2;

// lazily parse this as we don't need it right away
function add(a, b) {
  return a + b;
}

// oh looks like we do need add so lets go back and parse it
add(a, b);

所以这里我们的变量声明将是 eager parsed 但我们的函数是 lazily parsed。 这很好,直到我们得到 add(a, b) 因为我们需要我们的 add 函数,所以它会更快 eager parse add 马上。

要立即使用 eager parse add 函数,我们可以这样做:

// eager parse declarations right away
const a = 1;
const b = 2;

// eager parse this too
var add = (function(a, b) {
  return a + b;
})();

// we can use this right away as we have eager parsed
// already
add(a, b);

这就是您使用的大多数模块的创建方式。

函数内联

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

我们以下面的代码为例:

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

const callFunction100Times = (func) => {
  for(let i = 0; 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 函数更快,但实际情况是反优化步骤使执行时间更长。

对象

说到对象,V8 底层有一个类型系统来区分你的对象:

单态

对象具有相同的键,没有区别。

// mono example
const person = { name: 'John' }
const person2 = { name: 'Paul' }

多态性

这些对象具有相似的结构,但有一些小的差异。

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

巨变

对象完全不同,无法比较。

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

所以现在我们知道了 V8 中的不同对象,让我们看看 V8 是如何优化我们的对象的。

隐藏类

隐藏类是 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 在创建对象时所经历的过程,现在让我们看看如何优化对象并重用 classIds

创建对象的技巧

如果可以,您应该 在构造函数 中声明您的属性。 这将确保对象结构保持不变,以便 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 代码的知识。