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

V8是Google用来编译JavaScript的引擎。Firefox拥有自己的名为SpiderMonkey的引擎,与V8十分相似,但有所不同。我们将在本文中讨论V8引擎。

有关V8引擎的一些事实:

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

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

我画了下面这个图,图里显示了所有步骤,然后我们将详细讨论每个步骤:

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

解析JavaScript

因此,我们的JavaScript代码第一步是对其进行解析。让我们讨论一下到底是什么解析。

解析分为两种方式:

  • 非惰性(完全解析)-这会立即解析每行
  • 懒惰(预先解析)-进行最少的工作,解析我们需要的内容,其余部分留到以后

哪个更好?

让我们看一些代码。

// 立即解析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 wayfunction 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 wayfunction 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 exampleconst person = { name: 'John' }const person2 = { name: 'Paul' }

Polymorphism

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

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

Megamorphism

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

// mega exampleconst 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 createdconst p2 = new Point(33, 44)

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

const obj = { a: 1 } // hidden class createdobj.b = 3const obj2 = { b: 3 } // another hidden class createdobj2.a = 1// this would be betterconst obj = { a: 1 } // hidden class createdobj.b = 3const obj2 = { a: 1 } // hidden class is reusedobj2.b = 3

常规优化技巧

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

修复函数参数类型

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

function add(x,y) {  return x + y}add(1,2) // monomorphicadd('a', 'b') // polymorphicadd(true, false)add({},{})add([],[]) // megamorphic - at this stage, 4+ tries, no optimization will happen

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

// don't do thisfunction 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代码的一些知识。

原文: https://alligator.io/js/v8-engine/

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