V8引擎是如何执行一段Javascript代码的?

108 阅读4分钟

一、编译器和解析器

因为机器不能直接理解我们书写的代码,所以存在编译器和解释器。在程序执行之前,需要先将我们敲得代码翻译成机器能读懂的机器语言。按照语言的执行流程,可以把语言华为分编译型语言解释型语言

1.编译型语言

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

image.png

在编译型语言的编译过程中,编译器首先会对源代码进行词法分析,语法分析生成抽象语法树(AST),然后进行代码优化最后生成并输入机器可以理解的二进制文件,在编译过程中如果发生了语法或者其他错误,编译器会抛出异常并且不会生成二进制文件。

2.解释型语言

解释型语言所编写的程序在每次运行之前都需要通过解释器对程序进行动态编译和执行。

image.png

在解释型语言的解释过程中,也是先对源代码进行词法分析,语法分析生成抽象语法树(AST),然后基于抽象语法树生成字节码,最后根据字节码来执行程序输出结果。

二、V8是如何执行一段Javascript代码的

image.png

从图中可以可以看出来V8在执行的时候既有解释器Ignition,又有编译器TurboFan

下面是V8执行一段代码的流程:

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

ASTAbstract Syntax Tree(抽象语法树)的缩写。它是一种用于表示程序代码结构的树状数据结构。AST 是编译器和解释器等程序处理源代码的中间表示形式。

当程序代码被编写时,它遵循一定的语法规则。AST 通过将源代码解析为树形结构来表示这些语法规则。每个节点表示源代码中的一个语法结构,而树的结构反映了源代码的嵌套和层次关系。

JavaScript 代码片段:

function add(a, b) {
  return a + b;
}

对应的 AST:

Program
  └── FunctionDeclaration
        ├── Identifier (name: "add")
        ├── Identifier (name: "a")
        ├── Identifier (name: "b")
        └── BlockStatement
              └── ReturnStatement
                    └── BinaryExpression
                          ├── Identifier (name: "a")
                          ├── Identifier (name: "b")
                          └── Operator: "+"

在这个例子中, Program 是整个程序的顶级节点,它包含了一个 FunctionDeclaration 节点,表示一个函数声明。这个函数声明包含一个函数名(Identifier),两个参数(也是Identifier),以及一个函数体(BlockStatement)。在函数体中,有一个 ReturnStatement,返回一个 BinaryExpression,它包含两个操作数和一个运算符。

生成AST经过两个阶段。

第一阶段是分词(Tokenize): 也就是词法分析,将一行行的源码拆解成一个个的token(语法生不能再分的,最小的单个字符或者字符串)。 以 var name = '竹合‘为例子 会被拆分为'var','name', '=', '竹合'四个token。

第二阶段是解析(parse): 也就是语法分析,将上一步生成的token数据根据语法规则转换为AST,如果源码中存在语法错误,这一步将会终止,并抛出语法错误。

有了AST之后,V8引擎就会生成该段代码的执行上下文。

2.生成字节码

解释器会根据AST生成字节码,并解释执行字节码。

在最初V8并没有字节码,而是将AST直接转换为机器码,但是随着手机的普及,V8生成的机器码需要占用大量的内存,但是手机的内存小,为了解决内存占用问题,引入了字节码,并且抛弃了之前的编译器。

字节码是介于AST和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码之后才能执行

image.png 字节码的占用空间远远小于机器码的占用空间,所以就减少了系统内存的使用。

3.生成代码

生成字节码之后,然后就是进入执行阶段。

在执行字节码时,如果有一个段代码被重复执行多次(热点代码 HotSpot),那么后端编译器TurboFan就会把该段热点代码编译成机器码(高效)。

这种字节码配合解析器和编译器的称为即使编译(JIT)