JS代码是怎么被执行的

我们看到的JS都是在浏览器中或者在Node环境中运行的对吧,那不论是浏览器还是Node,负责编译并且解释执行JS代码的都是一个叫做V8的东西,所以这个问题其实就是V8引擎是怎么去运行JavaScript的,而js和C/C++/Go/Rust这类静态编译的语言不同,这些静态编译的语言通过编译器把代码变成机器码,然后在机器上运行,js呢在编译后会生成字节码,然后在v8的虚拟机上运行字节码,java和python也有自己的虚拟机实现,这些语言都将生成的字节码放在虚拟机上运行,相比于直接以机器码运行的语言,这些语言在损失了性能的同时又获得了更多功能上的遍历,然后我们回到V8引擎是如何执行JS的问题。

img

我们这里以V8引擎的模块实现为索引来讲

V8 的 Parser 模块

Parser是V8的一个子模块,它负责将JavaScript源码转换成AST

词法分析

在词法分析过程中会将由字符组成的字符串分解成的词法单元(Token)。

image-20230204122527701

Input:

1
2
// Life, Universe, and Everything
var answer = 42;

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[
{
"type": "Keyword",
"value": "var"
},
{
"type": "Identifier",
"value": "answer"
},
{
"type": "Punctuator",
"value": "="
},
{
"type": "Numeric",
"value": "42"
},
{
"type": "Punctuator",
"value": ";"
}
]
语法分析

将词法单元变成抽象语法树(Abstract Syntax Tree,AST)

Input 为词法分析 的 Output

Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "answer"
},
"init": {
"type": "Literal",
"value": 42,
"raw": "42"
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}

可以在这个网站自己动手看看token和AST的样子。

V8 的 Ignition && TurboFan 模块

Ignition:interpreter,即解释器,负责将AST转换为字节码(Bytecode)并解释执行。

字节码是介于AST和机器码的一种代码,需要通过解释器转换成机器码后执行。一开始V8并没有Bytecode这个中间过程,而是直接将AST转换成机器码,但是由于内存占用问题,虽然机器码效率最高,但机器码占用的内存空间远超过字节码,需要消耗大量内存来放转换后的字节码,所以V8团队选择了时间换空间的策略,转变成了现在的AST->Bytecode->机器码。

img

TurboFan:compiler,即编译器,利用Ignition所收集的类型信息,将Bytecode转换为优化的汇编代码。

那么Ignition作为解释器,可以完成AST到字节码的转换过程并且担任解释执行的工作,为什么V8还需要TurboFan这个编译器呢,我们不是说Javascript是一种解释型语言吗?

实际上呢在现代的编程语言中解释型语言像JS为了功能上的需要,会引入JIT这样的技术

JIT (Just-In-Time)技术

通常,如果有一段第一次执行的字节码,解释器 Ignition 会逐条解释执行。在执行字节码的过程中,如果发现有热点代码(HotSpot),比如一段代码被重复执行多次,这种就称为热点代码,那么后台的编译器 TurboFan 就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。这种采用字节码结合编译器和解释器的技术,就是Just-In-Time也就是JIT,Python和Java的虚拟机实现也是用了JIT技术的。

了解JIT后我们也能知道为什么解释器Interpreter和编译器Compiler要叫做Ignition和TurboFan了,这是因为解释器 Ignition 是点火器的意思,编译器 TurboFan 是涡轮增压的意思,寓意着代码启动时通过点火器慢慢发动,一旦启动,涡轮增压介入,其执行效率随着执行时间越来越高效率,因为热点代码都被编译器 TurboFan 转换了机器码,直接执行机器码就省去了字节码“翻译”为机器码的过程。

img

img