V8 是 Google 用于编译 JavaScript 的引擎。 Firefox 有自己的引擎 SpiderMonkey,它与 V8 非常相似,但也有区别。 我们将在本文中讨论 V8 引擎。
关于 V8 发动机的几个事实:
- 用 C++ 编写并用于 Chrome 和 Node.js(以及 最新版本的 Microsoft Edge )
- 实现 ECMA-262 中指定的 ECMAScript
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 代码的知识。