6月阅读周·WebKit技术内幕 | JavaScript引擎
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。
这个“玩法”虽然常见且板正,但是有效,已经坚持阅读五个月。
已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》。
当前阅读周书籍:《WebKit技术内幕》。
JavaScript引擎
概述
JavaScript语言
JavaScript是一种脚本语言,主要用在Web的客户端(这样说也不准确,Node.js和其他一些用法的出现就是例外),它的出现是为了控制网页客户端的逻辑,例如同用户的交互、异步通信等需求。当然,在HTML5高速发展的今天,它的作用越来越大,被广泛地使用在各种其他技术中。
JavaScript引擎
什么是JavaScript引擎?简单来讲,就是能够将JavaScript代码处理并执行的运行环境。
一个JavaScript引擎不外乎包括以下几个部分。
- 编译器。主要工作是将源代码编译成抽象语法树,在某些引擎中还包含将抽象语法树转换成字节码。
- 解释器。在某些引擎中,解释器主要是接收字节码,解释执行这个字节码,同时也依赖垃圾回收机制等。
- JIT工具。一个能够能够JIT的工具,将字节码或者抽象语法树转换成本地代码,当然它也需要依赖牢记。
- 垃圾回收器和分析工具(Profiler)。它们负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能和功效。
JavaScript引擎和渲染引擎
从模块上看,它们是两个独立的模块,分别负责不同的事情:JavaScript引擎负责执行JavaScript代码,而渲染引擎负责渲染网页。JavaScript引擎提供调用接口给渲染引擎,以便让渲染引擎使用JavaScript引擎来处理JavaScript代码并获取结果。这当然不是全部,事情也不是这么简单,JavaScript引擎需要能够访问渲染引擎构建的DOM树,所以JavaScript引擎通常需要提供桥接的接口,而渲染引擎则根据桥接接口来提供让JavaScript访问DOM的能力。在现在众多的HTML5能力中,很多都是通过JavaScript接口提供给开发者的,所以这部分同样需要根据桥接接口来实现具体类,以便让JavaScript引擎能够回调渲染引擎的具体实现。
V8引擎
基础
V8是一个开源项目,也是一个JavaScript引擎的实现。因为在当时之前的JavaScriptCore引擎和其他的JavaScript引擎的性能都不能令人非常满意。为了提高JavaScript代码的执行效率从而获得更好的网页浏览效果,它甚至采用直接将JavaScript编译成本地代码的方式。
1、应用程序编程接口(API)
- 各种各样的基础类:这里面包含对象引用类(如WeakReferenceCallbacks)、基本数据类型类(如Int32、Integer、Number、String、StringObject)和JavaScript对象(Object)。这些都是基础抽象类,没有包含实际的实现,真正的实现在“src”目录中的“objects.h/cc”中。
- Value:所有JavaScript数据和对象的基类,如上面的Integer、Number、String等。
- V8数据的句柄类:以上数据类型的对象在V8中有不同的生命周期,需要使用句柄来描述它们的生命周期,以及垃圾回收器如何使用句柄来管理这些数据,句柄类包括Local、Persistent和Handle。
- Isolate:这个类表示的是一个V8引擎实例,包括相关状态信息、堆等,总之这是一个能够执行JavaScript代码的类,它不能被多个线程同时访问,所以,如果非要这么做的话,需要使用锁。V8使用者可以使用创建多个该类的实例,但是每个实例之间就像这个类的名字一样,都是孤立的。
- Context:执行上下文,包含内置的对象和方法,如print方法等,还包括JavaScript内置的库,如math等。
- Extension:V8的扩展类。用于扩展JavaScript接口,V8使用者基于该类来实现相应接口,被V8引擎调用。
- Handle:句柄类,主要用来管理基础数据和对象,以便被垃圾回收器操作。主要有两个类型,一个是Local(就是Local类,继承自Handle类),另一个是Persistent(Persistent类,继承自Handle类)。前者表示本地栈上的数据,所以量级比较轻,后者表示函数间的数据和对象访问。
- Script:用于表示被编译过的JavaScript源代码,V8的内部表示。
- HandleScope:包含一组Handle的容器类,帮助一次性删除这些Handle,避免重复调用。
- FunctionTemplate:绑定C++函数到JavaScript,函数模板的一个例子就是将JavaScript接口的C++实现绑定到JavaScript引擎。
- ObjectTemplate:绑定C++对象到JavaScript,对象模板的典型应用是Chromium中将DOM节点通过该模板包装成JavaScript对象。
工作原理
在V8中,数据的表示分成两个部分,第一部分是数据的实际内容,它们是变长的,而且内容的类型也不一样,如String、对象等。第二个部分是数据的句柄,句柄的大小是固定的,句柄中包含指向数据的指针。为什么会是这种设计呢?主要是因为V8需要进行垃圾回收,并需要移动这些数据内容,如果直接使用指针的话就会出问题或者需要比较大的开销,使用句柄的话就不存在这些问题,只需要将句柄中的指针修改即可,使用者使用的还是句柄,它本身没有发生变化。
JavaScriptCore引擎
原理
JavaScriptCore引擎是WebKit中的默认JavaScript引擎,也是苹果在开源WebKit项目之后,开源的另外一个重要的项目。
从2008年开始,JavaScriptCore引擎开始一个新的优化工作,重新实现了编译器和字节码解释器,这就是SquirrelFish。该工作对于引擎的性能优化做了比较大的改进。随后,苹果内部代号为“Nitro”的JavaScript引擎也是基于JavaScriptCore项目的,它的性能还是非常出色的,鉴于其是内部项目,所以具体还有什么特别的处理就不得而知了。在这之后,开发者们又将内嵌缓存、基于正则表达式的JIT和简单的JIT引入到JavaScriptCore中。然后,又陆续加入了字节码解释器。可以看出,JavaScriptCore引擎也在不断地高速发展中。
内存管理
在JavaScriptCore中,内存管理和垃圾回收机制也随着其他技术的改变而发生着很大的变化。对于垃圾回收机制来说,最重大的改变就是像V8一样,引入了分代垃圾回收机制。所以,堆也会被分成几个分代。这样,当进行垃圾回收的时候,就不需要对所有对象进行标记。分代技术前面也讨论过了,而且很早就在其他虚拟机中使用,如Java虚拟机,它们思想都是类似的,这里不再赘述。
在V8中使用Zone来一次性释放内存,JavaScriptCore中也有类似的机制,那就是JSGlobalData,这里也不再过多的描述。
比较JavaScriptCore和V8
由于JavaScriptCore一直是Webkit的默认JavaScript引擎,所以被广泛应用。但是,随着Google发布Chrome的同时加上V8引擎,而且V8自出现后就是以性能作为目标,引入了众多新颖的技术,确实极大地推动了整个业界的JavaScript引擎性能的快速发展。但是,如果想用一句话说明V8和JavaScriptCore的优劣,这是很困难的。在很多领域,V8扮演着冲锋者的角色,但是JavaScriptCore依旧不断改进自己的技术和实现,同时在某些方面,因为使用了一些V8没有的东西,如字节码反而在某些情况下较容易优化。当然,这也不是绝对的。
总结
Web技术的发展让JavaScript语言远远超出了之前的设计初衷,在承载着越来越强大能力的同时,它的性能也越来越受到关注。
在介绍JavaScript语言的特性之后,逐步剖析现代JavaScript引擎的工作原理和为了提高性能所做的巨大努力。当然,本篇解释的重点主要是现在WebKit中广泛使用的JavaScriptCore引擎和V8引擎,两者有一些共通之处,却又有一些不同之处。
作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)