JavaScript 引擎简介

优质
小牛编辑
125浏览
2023-12-01

什么是JavaScript引擎?简单来讲,就是能够提供执行JavaScript代码的运行环境。要解释这一概念,需要了解一些编译原理的基础概念和现代语言需要的一些新编译技术。

首先来看C/C++语言。它们是比较悠久的语言了,实际上就是使用编译器直接将它们编译成本地代码,这一切都是由开发人员在代码编写完成之后实施。用户只是使用这些编译好的本地代码,这些本地代码被系统的加载器加载执行,这些本地代码(也就是机器指令)由操作系统调度CPU直接执行,无需其它额外的辅助虚拟机等。这一过程基本上是从源代码开始,然后抽象语法树,之后中间表示,最后到本地代码。

其次,来看看Python等脚本语言。处理脚本语言通常的做法是开发者将写好的代码直接交给用户,用户使用脚本的解释器将脚本文件加载然后解释执行。当然,现在Python也可以支持将脚本编译生成中间表示,当然这是后话。所以这表示,对于脚本语言,没有开发人员的编译过程,当然也不是绝对,这主要因为使用场景不一样,它的目的不是高性能。这一过程是源代码,到抽象语法树,再到解释器解释执行。

然后来看看Java语言。其做法可以理解为比较明显的两个阶段,首先是像C++语言一样的编译器,但是,同C++编译器生成的本地代码结果不同,经过编译器编译之后的是字节码,字节码是平台无关的。在运行字节码阶段,Java的运行环境也就是Java虚拟机会加载字节码,使用解释执行这些字节码。如果仅是这样,那Java的性能就差C++太多了。现代Java虚拟机一般都引入了JIT技术,也就是前面说的将字节码转变成本地代码来提高执行效率。这主要两个阶段,第一阶段对时间要求不严格,第二阶段则对每个步骤所花费的时间非常敏感,时间越短越好。

最后回到JavaScript语言上来。前面已经说了它是一种解释性脚本语言。是的,它的确是,但是随着众多工程师不断投入资源来提高它的速度,这使得它能够使用了Java虚拟机和C++编译器中众多的技术,它的工作方式也在演变。早期也是一样由解释器来解释它们即可,就是将源代码转变成抽象语法树,然后在抽象语法树上解释执行。随着将Java虚拟机的JIT技术引入,现在的做法是将抽象语法树转成中间表示(也就是字节码),然后通过JIT技术转成本地代码,这能够大大的提高了执行效率。当然也有些做法直接从抽象语法树生成本地代码的JIT技术,例如V8。这是因为JavaScript跟Java还是有以下一些区别的:

其一当然是类型,JavaScript是无类型的语言,这使得对于对象的表示和属性的访问上比Java存在比较大的性能损失。不过现在有一些新的技术,参考C++或者Java的类型系统的优点,构建隐式的类型信息,这些后面逐一介绍。

其二是Java语言通常是将源代码编译成字节码,这个同执行阶段是分开的,也就是从源代码到抽象语法树到字节码这段时间的长短是无所谓的(或者说不是特别重要),所以主要是尽可能的生成高效的字节码即可。而对于JavaScript而言,这些都是在网页和JavaScript文件下载后同执行阶段一起在网页的加载和渲染过程中来实施的,所以对它们的处理时间也有着很高的要求。

描述JavaScript代码执行的过程,这一过程中因为不同技术的引入,导致非常复杂,而且因为都是在代码运行过程中来处理这些步骤,所以每个阶段时间越少越好,而且每引入一个阶段都是额外的时间开销,可能最后的本地代码执行效率很高,但是之前步骤如果耗费太多时间的话,最后的执行结果可能并不会好。所以不同的JavaScript引擎选择了不同的路径,这里先不仔细介绍,后面再描述它们。

所以一个JavaScript引擎不外乎包括以下部分:

第一, 编译器。主要工作是将源代码编译成抽象语法树,然后在某些引擎中还包含将抽象语法树转换成字节码。

第二, 解释器。在某些引擎中,解释器主要是接受字节码,解释执行这个字节码,然后也依赖来及回收机制等。

第三, JIT工具。一个能够能够JIT的工具,将字节码或者抽象语法树转换成本地代码,当然它也需要依赖牢记

第四, 垃圾回收器和分析工具(profiler)。它们负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能和功效。

JavaScript引擎和渲染引擎

前面介绍了网页的工作过程需要使用到两个引擎,也就是渲染引擎和JavaScript引擎。从模块上看,目前,它们是两个独立的模块,分别负责不同的事情:JavaScript引擎负责执行JavaScript代码,而渲染引擎负责渲染网页。JavaScript引擎提供调用接口被渲染引擎使用,渲染引擎使用JavaScript引擎来处理JavaScript代码并获取结果。这当然不是全部,事情不是这么简单,JavaScript引擎需要能够访问渲染引擎构建的DOM树,所以JavaScript引擎通常需要提供桥接的接口,而渲染引擎则根据桥接接口来提供让JavaScript访问DOM的能力。在现在众多的HTML5能力中,很多都是通过JavaScript接口提供给开发者的,所以这部分同样需要根据桥接接口来实现具体类,以便让JavaScript引擎能够回调渲染引擎的具体实现。下图是一个简单的关于两者引擎的关系。 enter image description here 在WebKit/Blink项目中,这两种引擎通过桥接接口来访问DOM结构,这对性能来说是一个重大的损失,因为每次JavaScript代码访问DOM都需要通过复杂和低效的桥接接口来完成。鉴于访问DOM树的普遍性,这是一个常见的问题。希望以后可以减少这一方面的开销。

JavaScriptCore引擎和V8引擎

WebKit中的JavaScript引擎

在WebKit项目中,最初只有JavaScriptCore引擎。在Blink还独立出来之前,为了支持不同的JavaScript引擎,WebKit设计了一套接口可以切换使用不同的JavaScript引擎(事实上,这一接口会降低性能),所以,WebKit当时可以支持两种类型的JavaScript引擎,那就是JavaScriptCore引擎和V8引擎。两者都是基于WebKit所提供的接口来同渲染引擎协同工作。

JavaScriptCore引擎

JavaScriptCore引擎是WebKit中默认的引擎,在早期阶段,它的性能不是特别突出。特别是,它只有解释器来解释执行JavaScript代码。这一效率十分的低效,当然从2008年开始,JavaScriptCore引擎开始一个新的优化工作,就是重新实现了编译器和字节码解释器,这就是SquirrelFish。该工作对于引擎的性能优化作了比较大的改进。随后,苹果内部代号为”Nitro”的JavaScript引擎也是基于JavaScriptCore项目的,它的性能还是非常出色的,鉴于其是内部项目,所以具体还有什么特别的处理就不得而知了。在这之后,开发者们又将内嵌缓存、基于正则表达式的JIT和简单的JIT引入到JavaScriptCore中。然后,又陆续加入的字节码解释器。可以看出,JavaScriptCore引擎也在不断的高速发展中。

enter image description here

上图是JavaScriptCore最简单的处理部分,它主要是将源代码翻译成抽象语法树,之后是平台无关的字节码,在最初的版本中,字节码会被JavaScriptCore引擎解释执行。在后面的版本中,逐渐加入了JIT编译器,将热点函数生成本地代码,后面再详细介绍它们。

V8引擎

V8是一个开源项目,也是一个JavaScript引擎的实现。最开始是一帮语言方面的专家设计出来的,之后被Google收购,成为了JavaScript引擎和众多相关技术的引领者。它的目的很简单,就是为了提高性能,除了性能还是性能。因为之前不管JavaScriptCore引擎还是其它JavaScript引擎,在当时的情况下,它们的性能都不能令人非常满意。为了达到高性能的JavaScript代码执行效率从而获得更好的网页浏览效果,它甚至采用直接将JavaScript编译成本地代码的方式。

V8支持众多的操作系统,包括但是不限于Windows、Linux、Android、Mac OS X等。同时它也是能够支持众多的硬件架构,例如IA32、X64、ARM、MIPS等。这么看下来,它将主流软硬件平台一网打尽,由于它是一个开源项目,开发者可以自由的使用它的强大能力,一个例子就是目前炙手可热的NodeJS项目,它就是基于V8项目的。开源的好处就是大家可以很方便地学习、贡献和使用,下图是V8引擎最基础的代码执行过程。

enter image description here

从图中可以看出,首先它也是将源代码转变成抽象语法树的,这一点同JavaScriptCore引擎一样,之后两个引擎开始分道扬镳。不同于JavaScriptCore引擎,V8引擎并不将抽象语法树转变成字节码或者其它中间表示,而是通过JIT编译器的全代码生成器(full code generator)从抽象语法树直接生成本地代码,所以没有像Java一样的虚拟机或者字节码解释器。这样做的原因,主要是因为减少这抽象语法树到字节码的转换时间,这一切都在网页加载时候完成,虽然可以提高优化的可能,但是这些分析可能带来巨大的时间浪费。当然,缺点也很明显,至少包括两点:第一是某些JavaScript使用场景其实使用解释器更为合适,因为没有必要生成本地代码;第二是因为没有中间表示,会减少优化的机会因为缺少一个中间表示层。至于有些文章说的丧失了移植性,个人觉得对于JavaScript这种语言来说不是问题,因为并没有将JavaScript代码先编译然后再运行的明显两个分开阶段的用法,例如像Java语言那样。但是,针对V8设计思想来说,笔者认为它的理念比较先进,做法虽然比较激进,但是确实给JavaScript引擎设计者们带来很多新思路。

在之后的版本中,V8工程师们引入了Crankshaft编译器,它能够对热点的JS函数进行生成的分析和优化后再生成本地代码,原因在于不是所有的JavaScript代码都合适做如此深层次的优化,因为优化本身也需要花费一定的时间。有关V8的细节,希望以后有时间能够介绍它们。

目前V8和JavaScriptCore引擎各有特色,相信两者的发展能够不断推进JavaScript语言的执行性能。