共计 3988 个字符,预计需要花费 10 分钟才能阅读完成。
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 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 代码的一些知识。
原文:https://alligator.io/js/v8-engine/
最后照旧是一个广告贴,最近新开了一个分享技术的公众号,欢迎大家关注????(目前关注人数可怜????)