编译器和解释器
本文涉及到的概念有 编译器(Compiler)、解释器(Interpreter)、抽象语法树(AST)、字节码(Bytecode)、即时编译器(JIT)
JavaScript是一个编译和解释都存在的一个语言。
V8是如何执行一段JavaScript代码的
V8的执行过程中是既有解释器 Ignition ,又有编译器 TurboFan。分析一下上面的流程:
-
生成抽象语法树AST和执行上下文
AST是如何生成的,分为两个阶段。
- 第一个阶段是::分词(tokenize)::,也叫词法分析,实际就是将源码拆解成一个个token。
上面的 var
,myName
,=
,”极客时间”
- 第二个阶段是::解析(parse)::,又称为语法分析,作用是将上一步生成的token数据,根据语法规则转换成AST。源码符合语法规则,就转换成功,若源码有语法作物,就会终止,并抛出一个语法错误。
::此时将源代码转换成抽象语法树,并生成对应的这段代码的执行上下文::。抽象语法树AST类似是渲染引擎处理完HTML后生成计算机可理解的DOM树
结合代码
var myName = "极客时间"
function foo(){
return 23;
}
myName = "geektime"
foo()
复制代码
以上这段代码生成AST如下
AST 的结构和代码的结构非常相似,其实你也可以把 AST 看成代码的结构化的表示,编译器或者解释器后续的工作都需要依赖于 AST,而不是源代码
AST是一种非常重要的数据结构,在很多项目中都有使用,::比如Babel,一个用于将ES6转换为ES5的代码转换器,原理就是讲ES6源码先转换成AST,然后再将ES6代码转换成的AST转换成ES5语法的AST,最后根据ES5的AST生成字节码,又比如ESLint,一个用于检查JavaScript代码规范的插件,检查流程也是先将源码转换成AST,然后利用AST来检查代码的规范化。::
-
生成字节码
此时已经有了AST和执行上下文,然后就是::解释器Ignition,用来转换AST生成字节码,并且解释执行字节码::。
为什么要有字节码?
一开始V8并没有字节码,全部转换为机器码,虽然这样代码的执行效率很高,但是由于全转为机器码太占内存,所以就引入了字节码。
可以看到同样的代码字节码比机器码要更节约空间,但是字节码可以减少系统的内存使用。
-
执行代码
生成字节码后,就进入执行阶段了。
对于第一执行的字节码,解释器Ignition会逐条解释执行,::解释器Ignition除了负责生成字节码之外,还可以解释执行字节码。::在解释器执行过程中如果发现有热点代码,::比如一段代码重复执行多次,后台的编译器TurboFan就会将这部分字节码转换为机器码并且保存下来::,然后再执行这部分热点代码的时候,只需要执行编译器转换后的机器码就可以了,会大大提升代码的执行效率。
字节码结合解释器和编译器这种技术叫做即时编译(JIT),在Java和Python中的虚拟机也是基于这种技术实现的。在V8中,就是解释器在执行字节码的同时,检测代码情况,当发现有一部分代码变热之后,编译器就会将热点代码对应的字节码转换为机器码保存起来,下次再执行就直接执行机器码。
JIT工作机制