从某种意义上来说,Node.js 并不是一个从零开始编写的 JavaScript 运行时,它其实也是站在“巨人的肩膀”上进行了一系列的拼凑和封装得到的结果。它的高效离不开一些很牛的第三方程序和类库。比如本文我们介绍的Chrome V8。
Chrome V8 简称 V8,是由谷歌开源的一个高性能 JavaScript 引擎。该引擎采用 C++ 编写,Google Chrome 浏览器用的就是这个引擎。V8 可以单独运行,也可以嵌入 C++ 应用当中。
和其他的 JavaScript 引擎一样,V8 会编译、执行 JavaScript 代码,并一样会管理内存、垃圾回收等。
就是因为 V8 的高性能以及跨平台等特性,所以它也是 Node.js 的 JavaScript 引擎。
高效
V8 开发小组由一群程序语言专家组成。其中核心工程师 Lars Bak 之前在 Sun 公司工作,专注于 Java 虚拟机加速技术的研究,产出了 HotSpot,除此之外,他还曾开发了 Strongtalk1。
所以,V8 的代码里面蕴含了从 HotSpot 和 Strongtalk 中汲取的精髓。
该研发小组从 2006 年开始研发 V8,原因是当年市面上的各种 JavaScript 引擎效率都比较低下。在 Lars Bak 等人的贡献下,JavaScript 引擎添加了新的一员—— Chrome V8,并且效率非常高。
V8 的高效主要体现在以下 4 个特性上面。
(1) JIT 编译
JIT 编译,全称 Just-In-Time 编译,也就是即时编译。它编译出的结果直接是机器语言,而不是字节码。这样大大提高了 V8 在执行 JavaScript 时的效率。不过后来其他的几家 JavaScript引擎也渐渐推出了对 JIT 的支持。
(2) 垃圾回收
这个特性在 Java 领域中使用得比较多。虽然其他语言或者其他的 JavaScript 引擎实现都有垃圾回收,但是 V8 的垃圾回收借鉴了 Java VM 的精确垃圾回收管理,而其他很多语言的垃圾回收用的是保守垃圾管理。
相较而言,V8 的这套垃圾回收机制的效率要远远高于其他一些垃圾回收机制实现——实际上代价就是这种机制的实现难度更大。
(3) 内联缓存(Inline Cache)
V8 使用了内联缓存的特性来提高属性的访问效率。如有一个访问是 this. 蛋花汤,没有内联缓存的时候,每次要取蛋花汤的话都会对哈希表进行一次寻址,而加入了内联缓存的特性之后,V8 能马上知道这个属性的一个偏移量,而不用再次计算寻址的偏移量了。
(4) 隐藏类
由于 JavaScript 是一门动态的编程语言,因此哪怕是在 ES6 及以上版本的规范中有了class 的一个定义,开发者也能非常方便地对一个对象添加或者移除一个属性。
隐藏类就是对这样一套对象体系中的一个黑科技的包装——所有如属性一样的对象会被归为同一个隐藏类。
下面举个简单的例子:
一开始根据 Pet 创建了 蛋花汤 这个对象。在最开始初始化的时候 V8 就会创建一个隐藏类(假设是 P0),这是一个空类,因为它还没有任何的属性;后来 this.type = type 执行了,隐藏类就有了 type 属性,这个时候就又多了一个 P1 的隐藏类——P1 是基于 P0 创建的,并且多了 type 属性;接着,name 被赋值上去,于是隐藏类又多了一个 P2。
然后在创建 南瓜饼 对象的时候,又走了上面的老路,只不过这次不是创建隐藏类 P0、P1和 P2 了,而是直接沿用它们。在初始化 南瓜饼 的时候,它依次会属于上面创建的3 个隐藏类,直到最后它跟 蛋花汤 一样都属于 P2。
最后一行代码在给 蛋花汤 赋值 age 的时候,又一个新的隐藏类 P3 会被创建。这个时候 蛋花汤 和 南瓜饼 分别属于 P3 和 P2。这些描述分别如下图。
最开始的蛋花汤和南瓜饼隐藏类归属
赋值 type 后的蛋花汤和南瓜饼隐藏类归属
赋值 name 后的蛋花汤和南瓜饼隐藏类归属
最终的蛋花汤和南瓜饼隐藏类归属
隐藏类和内联缓存这两把“匕首”联合起来,是 V8 高效的一个非常重要的原因,因为同一个隐藏类的对象们能用同一套内联缓存来寻址。
遵循 ECMAScript
在当前 V8 的项目主页中,有一句话表明了它是遵循 ECMA-262 标准的:
“V8 implements ECMAScript as specified in ECMA-262.”
就目前来说 ECMA-262 标准(曾)发布了 7 个大版本。
Generator 函数的爱称,因其有一个显著的标识——形如菊花的星号(*)而得名。
V8 在开发的过程中也一直追着 ECMAScript 发布的脚步,如基本上完成了对 ES6 的支持,而且最新版也对 async/await 函数进行了支持。
也正是因为 V8 对 ECMAScript 标准紧追不舍,才有了 Node.js 能及时跟上 ECMAScript 最新语法的情况。如 Node.js 7.6 正式默认支持 async/await 功能就是沾了 V8 的光。
Node.js 与 Chrome V8
下面是 V8 与 Node.js 的部分版本对照表。
Node.js 一直紧跟 V8 的版本脚步在迭代。
Node.js 与 V8 实际上看起来更像是一对情侣,而不仅仅是 Node.js 一厢情愿地使用 V8 作为自己的底层支持。
在 Chrome V8 的博客中曾经有一篇文章名为《V8 Node.js》。Node.js 在几年发展中的流行度稳步增长,于是有了 V8 的“姑娘,你成功引起了我的注意”。现在 V8 也有一些工作是为 Node.js 而做的:
--在 Chrome 开发者工具中可以调试 Node.js;
--加速 ES6;
--针对 Node.js vm 模块和 REPL 的一些修复;
--Async / await。
本文选自《Node.js:来一打 C++ 扩展》
作者:博文视点
链接:https://www.jianshu.com/p/8290715feec6