JS代码是怎么被执行的
我们看到的JS都是在浏览器中或者在Node环境中运行的对吧,那不论是浏览器还是Node,负责编译并且解释执行JS代码的都是一个叫做V8的东西,所以这个问题其实就是V8引擎是怎么去运行JavaScript的,而js和C/C++/Go/Rust这类静态编译的语言不同,这些静态编译的语言通过编译器把代码变成机器码,然后在机器上运行,js呢在编译后会生成字节码,然后在v8的虚拟机上运行字节码,java和python也有自己的虚拟机实现,这些语言都将生成的字节码放在虚拟机上运行,相比于直接以机器码运行的语言,这些语言在损失了性能的同时又获得了更多功能上的遍历,然后我们回到V8引擎是如何执行JS的问题。
我们这里以V8引擎的模块实现为索引来讲
V8 的 Parser 模块
Parser是V8的一个子模块,它负责将JavaScript源码转换成AST。
词法分析
在词法分析过程中会将由字符组成的字符串分解成的词法单元(Token)。
Input:
1 | // Life, Universe, and Everything |
Output:
1 | [ |
语法分析
将词法单元变成抽象语法树(Abstract Syntax Tree,AST)
Input 为词法分析 的 Output
Output
1 | { |
可以在这个网站自己动手看看token和AST的样子。
V8 的 Ignition && TurboFan 模块
Ignition:interpreter,即解释器,负责将AST转换为字节码(Bytecode)并解释执行。
字节码是介于AST和机器码的一种代码,需要通过解释器转换成机器码后执行。一开始V8并没有Bytecode这个中间过程,而是直接将AST转换成机器码,但是由于内存占用问题,虽然机器码效率最高,但机器码占用的内存空间远超过字节码,需要消耗大量内存来放转换后的字节码,所以V8团队选择了时间换空间的策略,转变成了现在的AST->Bytecode->机器码。
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 转换了机器码,直接执行机器码就省去了字节码“翻译”为机器码的过程。