JavaScript 引擎的工作机制
JavaScript 引擎的基本工作流程,可分为解析、解释和优化 3 个步骤。
解析
解析步骤又可以拆分成 2 个小步骤:
-
词法分析,将 JavaScript 代码解析成一个个的令牌(Token);
-
语法分析,将令牌组装成一棵抽象的语法树(AST)。
下面是一段简单的代码,声明了一个字符串变量并调用函数 console.log 进行打印。
var name = 'web'
console.log(name)
通过词法分析会对这段代码逐个字符进行解析,生成类似下面结构的令牌(Token),这些令牌类型各不相同,有关键字、标识符、符号、字符串。
Keyword(var)
Identifier(name)
Punctuator(=)
String('web')
Identifier(console)
Punctuator(.)
Identifier(log)
Punctuator(()
Identifier(name)
Punctuator())
语法分析阶段会用令牌生成类似下面结构的抽象语法树,生成树的过程并不是简单地把所有令牌都添加到树上,而是去除了不必要的符号令牌之后,按照语法规则来生成。
解释
JavaScript 引擎是通过解释器 Ignition 将 AST 转换成字节码。字节码是对机器码的一个抽象描述,相对于机器码而言,它的代码量更小,从而可以减少内存消耗。
下面代码是从示例代码生成的字节码中截取的一段。它的语法已经非常接近汇编语言了,有很多操作符,比如 StackCheck、Star、Return。考虑这些操作符过于底层,涉及处理器的累加器及寄存器操作,已经超出前端范围。
[generated bytecode for function: log (0x1e680d83fc59 <SharedFunctionInfo log>)]
Parameter count 1
Register count 6
Frame size 48
9646 E> 0x376a94a60ea6 @ 0 : a7 StackCheck
......
0x376a94a60ec9 @ 35 : 26 f6 Star r5
9683 E> 0x376a94a60ecb @ 37 : 5a f9 02 f7 f6 06 CallProperty2 r2, <this>, r4, r5, [6]
0x376a94a60ed1 @ 43 : 0d LdaUndefined
9729 S> 0x376a94a60ed2 @ 44 : ab Return
Constant pool (size = 3)
Handler Table (size = 0)
Source Position Table (size = 24)
优化
解释器在得到 AST 之后,会按需进行解释和执行,也就是说如果某个函数没有被调用,则不会去解释执行它。
在这个过程中解释器会将一些重复可优化的操作(比如类型判断)收集起来生成分析数据,然后将生成的字节码和分析数据传给编译器 TurboFan,编译器会依据分析数据来生成高度优化的机器码。
优化后的机器码的作用和缓存很类似,当解释器再次遇到相同的内容时,就可以直接执行优化后的机器码。当然优化后的代码有时可能会无法运行(比如函数参数类型改变),那么会再次反优化为字节码交给解释器。
整个过程如下面流程图所示: