本文主要简单介绍一下V8引擎如何执行一段Javascript代码的。

要深入理解V8的工作原理,需要搞清楚一些概念和原理,比如编译器、解释器、抽象语法树(AST)、字节码(Bytecode)、即时编译器(JIT)等概念。

一、编译器和解释器

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

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

解释型语言编写的程序,在每次运行的时候都需要解释器对程序进行动态解释和执行。比如Python、Javascript都是解释型语言。

image.png

通过上图可以二者的大概流程,大致如下:

1、编译型语言的编译过程中,编译器首先会一次对源代码进行词法分析、语法分析,生成抽象语法树(AST),然后时候优化代码,最后再生成处理器能够理解的机器码(二进制文件)。如果编译成功,将会生成一个可执行的文件。但如果编译过程发生了错误,最后就不会生成二进制文件。
2、解释型语言再解释过程中,同样解释器也会对源代码进行词法分析、语法分析,并生成抽象语法树(AST),不过他会基于抽象语法树生成字节码,最后根据字节码来执行程序、输出结果。

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

通过以上介绍,已经对编译器和解释器有了一定的概念理解。可以通过如下的图来分析一下一段Javascript是如何执行的。

image.png

从上图中可以清楚的看出,V8在执行过程中既有解释器Ignition,又有编译器TurboFan,那么它们是如何配合去执行一段Javascript代码呢?接下来我们就按照上图来一一分析执行流程。

1、生成抽象语法树和执行上下文

将源代码转换成抽象语法树(AST),并生成执行上下文,而执行上下文主要是代码执行过程中的环境信息。
高级语言是开发者可以理解的语言,但是让编译器或者解释器来理解就非常困难了。对于编译器或者解释器来说,他们可以理解的就是AST信息。所以无论是编译型语言还是解释型语言,在编译过程中他们都会生成一个AST。这和渲染引擎将HTML格式文件转换成计算机可以理解的DOM树的情况类似。

生成AST语法树需要经过两个阶段:分词和解释

第一阶段就是分词阶段,又称为语法分析,它的作用就是将一行行的源码拆解成一个个的token。所谓token指的是语法上最小的单个字符或者字符串。

第二阶段 是解释阶段,又称为语法分析,它的作用就是将上一步词法分析得到的token数据,根据语法规则转为AST语法树。如果源码符合语法规则 就会顺利完成该步骤;如果出现语法错误就会在这一步终止。

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

2、生成字节码

有了抽象语法树和执行上下文后,接下来的第二步,解释器Ignition就登场了,它会根据AST生成字节码,并执行字节码。
其实一开始V8并没有字节码,而是将AST转换为机器码,由于执行机器码的效率是非常高效的,所以这种方式在发布一段时间内运行时非常好的。但是随着移动端浏览器的逐步普及,内存占有的问题也暴露出来了,因为V8需要消耗大量的内存来存放转换后的机器码。为了解决内存占用的问题,V8团队引入字节码,并且抛弃之前的编译器架构,来实现现在这套架构。

什么是字节码呢?为什么字节码的引入能解决内存占有的问题呢?

字节码就是介于AST和机器码之间的一种代码,字节码需要通过解释器将其转换成机器码之后才能执行。

image.png
从上图可以得出,机器码所占有的空间远远大于字节码,所以字节码 可以减少系统内存的使用。

3、执行代码

生成字节码之后,接下来就要进入执行阶段了。

通常,如果有一段第一次执行的字节码,解释器 Ignition会逐条解释执行。解释器Ignition除了负责生成字节码之外它还有一个作用就是解释执行字节码。在解释器Ignition执行字节码的过程中,如果发现有热点代码(HotSpot),比如有一段代码被重复执行了多次,这种代码就称之为热点代码。当存在热点代码时,后台的编译器TurboFan就会把该段热点代码编译成 更为高效的机器码,然后当执行这段被优化的代码时,只需执行编译后的机器码就行,这样就大大提升了代码的执行效率。

为什么经过编译器编译过后的代码执行会效率高呢?因为热点代码被编译为机器码之后,就可以直接执行机器码,这样就省去了字节码翻译为机器码的过程。

字节码配合解释器和编译器是最近很火的技术,我们把这种技术称之为 即时编译(JIT)。具体到V8中就是指解释器Ignition在解释执行字节码的同时,收集代码信息,当它发现某一部分代码变热了之后,编译器TurboFan就开始工作了,把热点的字节码转换为机器码,并把机器码保存起来,比备再次使用。

很多语言的工作引擎都是使用了字节码加上JIT技术,因此理解 JIT这套工作机制还是很有必要的。可以根据如下图来看JIT的工作过程:
image.png

三、Javascript的性能优化

对于优化Javascript执行效率,应该将优化的中心聚焦在单次执行脚本的执行时间和脚本的网络下载上,主要应该关注一下三点内容:
1、提升单次脚本的执行速度,避免Javascript的长任务霸占主线程,这样可以达到更快的页面相应;
2、避免大的内联脚本,因为在解析HTML的过程中,编译和解析也会占用主线程;
3、较少Javascript文件的容量,更小的文件会提升下载速度和占用更低的内存。