Skip to content
On this page

v8 工作原理

编译器和解析器

编译型语言在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了。比如 C/C++、GO 等都是编译型语言。

而由解释型语言编写的程序,在每次运行时都需要通过解释器对程序进行动态解释和执行。比如 Python、JavaScript 等都属于解释型语言。

从图中你可以看出这二者的执行流程,大致可阐述为如下:

  1. 在编译型语言的编译过程中,编译器首先会依次对源代码进行词法分析、语法分析,生成抽象语法树(AST),然后是优化代码,最后再生成处理器能够理解的机器码。如果编译成功,将会生成一个可执行的文件。但如果编译过程发生了语法或者其他的错误,那么编译器就会抛出异常,最后的二进制文件也不会生成成功。

  2. 在解释型语言的解释过程中,同样解释器也会对源代码进行词法分析、语法分析,并生成抽象语法树(AST),不过它会再基于抽象语法树生成字节码,最后再根据字节码来执行程序、输出结果。

v8 如何执行一段代码

生成抽象语法树(AST)和执行上下文

不管是编译器还是解析器,第一步都会把源代码转为结构化的数据(AST), 编译器或者解释器后续的工作都需要依赖于AST,而不是源代码,比如下面的代码转为AST如下:

js

var myName = "极客时间"
function foo(){
  return 23;
}
myName = "geektime"
foo()

生成AST如下:

比如我们常见的Babel和ESlint也是把原代码转为AST才进行后续的转化处理的。从源代码到AST的过程大概如下:

第一阶段是分词(tokenize),又称为词法分析,其作用是将一行行的源码拆解成一个个 token。所谓 token,指的是语法上不可能再分的、最小的单个字符或字符串。你可以参考下图来更好地理解什么 token:

第二阶段是解析(parse),又称为语法分析,其作用是将上一步生成的 token 数据,根据语法规则转为 AST。如果源码符合语法规则,这一步就会顺利完成。但如果源码存在语法错误,这一步就会终止,并抛出一个“语法错误”。这就是 AST 的生成过程,先分词,再解析。

生成字节码

在生成AST后, 解析器(lgnition)会根据AST生成对应的字节码。字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。 之所以要生成字节码,而不直接生成机器码,是为了优化内存占用,应该机器码更加占用内存。

执行代码阶段

接下来,解析器会逐行执行字节码,生成机器码并且执行。如果解析过程遇到需要经常解析的代码,v8引擎会调用编译器TurboFan把这段代码提前生成机器码并保存,下次调用时执行机器码,跳过字节码解析的过程,起到一定的优化。这样代码成为热点代码 (HotSpot)

其实字节码配合解释器和编译器是最近一段时间很火的技术,比如 Java 和 Python 的虚拟机也都是基于这种技术实现的,我们把这种技术称为即时编译(JIT)。

JIT的解析过程可以表示如下图:

总结

大致执行流程如下: