????????之前简单总结了JS的执行机制,在Chrome浏览器中JS代码的执行离不开V8引擎,那么V8是如何工作的呢?本文将总结下这个过程。
????????由于于机器并不能理解使用高级语言所写的代码,执行程序前需要一个"翻译"过程,按照执行流程不同可以分为编译型语言(C/C++、GO)和解释型语言(Python、JavaScript)。前者编写程序执行前,通过编译器的编译过程(此过程编译器依次对源码进行词法分析、语法分析,生成抽象语法树AST,然后优化代码生成处理器可理解的机器码,编译成功才有二进制文件),输出并保存机器能懂的二进制文件,后续程序运行都是直接运行二进制文件,不再重新编译;后者每次运行则需要通过解释器对程序进行动态解释和执行(解释过程:解释器对源码进行词法分析、语法分析,生成抽象语法树AST,基于AST生成字节码,由字节码执行程序输出结果)。
????????V8引擎执行一段JavaScript代码过程如下,既有解释器Ignition,也有编译器TurboFan[1]:
????????首先,源代码经过词法分析、语法分析转换成抽象语法树,并生成执行上下文(执行过程环境信息)。编译型语言or解释型,都需要转换为AST才能被编译器和解释器理解,这种数据结构具体是什么样子呢?可以通过JavaScript-ast(https://www.jointjs.com/demos/abstract-syntax-tree)站点生成AST,如下图。从中可以看出AST与代码结构类似,是代码的结构化表示。
????????抽象语法树的生成主要有分词(词法分析)和解析(语法分析)像个过程。首先,分词会逐行将源码拆解为一个个token(语法上不可再分的单个字符或字符串),图中"var","a","=","function"表示不同属性的token。然后,解析阶段会利用分词产生的token生成AST。
此处插一句:AST是一种重要数据结构,代码转码器Babel就是借助AST将ES6代码转换为ES5代码。具体实现原理:现将ES6源码转换为AST,然后再将ES6语法的AST转换为ES5语法的AST,最后利用ES5的AST生成JavaScript源代码。
ESLint的检测流程也需要将源码转换为AST,再利用AST来检测代码规范化问题。
????????有了AST和执行上下文后,解释器Ignition会利用AST生成字节码。字节码是一种介于AST和机器码之间的代码,需要通过解释器将其转换为机器码后才能执行。
????????机器码的执行效率更高,但占用的空间远超字节码,所以为减少系统内存使用使用了字节码(最初V8只直接将AST转换为机器码,但是由于V8需要消耗大量内存存放机器码,对于运行在512M内存的手机上内存占用问题日益明显)。
????????字节码生成后,解释器Ignition(除了生成字节码还可以解释执行)首先会逐行解释执行字节码;而后再Ignition执行字节码的过程中,如果发现热点代码(HotSpot,一段代码被反复执行多次),后台的编译器TurboFan会将该段热点字节码编译为执行效率更高的机器码。将热点代码通过编译器转换为机器码这一过程,也导致V8引擎具有"执行时间越久,执行效率越高"的特点,正是更多的代码成为机器码
这种字节码配合解释器和编译器的工作机制被称为即时编译(JIT),Java和Python的虚拟机也是用了这种技术实现。"字节码+JIT"技术引入了字节码,相比之前的V8将JS代码全部编译成机器码,就可以在内存和执行速度之间调节的弹性。
编译的基本单位是全局代码,或者函数!比如下载完一个js文件,先编译这个js文件,但是js文件内定义的函数是不会编译的。等调用到该函数的时候,Javascript引擎才会去编译该函数!
[1] 编译器和解释器:V8是如何执行一段JavaScript代码的?https://time.geekbang.org/column/article/131887