- Node.js:来一打 C++ 扩展
- 死月
- 3834字
- 2020-08-27 17:13:37
1.3 Node.js依赖简介
从某种意义上来说,Node.js并不是一个从零开始编写的JavaScript运行时,它其实也是站在“巨人的肩膀”上进行了一系列的拼凑和封装得到的结果。它的高效离不开一些很牛的第三方程序和类库。
1.3.1 Chrome V8
Chrome V8简称V8,是由谷歌开源的一个高性能JavaScript引擎。该引擎采用C++编写,Google Chrome浏览器用的就是这个引擎。V8可以单独运行,也可以嵌入C++应用当中。其Logo如图1-5所示。
图1-5 V8的Logo
和其他的JavaScript引擎一样,V8会编译、执行JavaScript代码,并一样会管理内存、垃圾回收等。
就是因为V8的高性能以及跨平台等特性,所以它也是Node.js的JavaScript引擎。
1.高效
V8开发小组由一群程序语言专家组成。其中核心工程师Lars Bak之前在Sun公司工作,专注于Java虚拟机加速技术的研究,产出了HotSpot,除此之外,他还曾开发了Strongtalk。
所以,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。这些描述分别如图1-6、图1-7、图1-8和图1-9所示。
图1-6 最开始的蛋花汤和南瓜饼隐藏类归属
图1-7 赋值type后的蛋花汤和南瓜饼隐藏类归属
图1-8 赋值name后的蛋花汤和南瓜饼隐藏类归属
图1-9 最终的蛋花汤和南瓜饼隐藏类归属
隐藏类和内联缓存这两把“匕首”联合起来,是V8高效的一个非常重要的原因,因为同一个隐藏类的对象们能用同一套内联缓存来寻址。
2.遵循ECMAScript
在当前V8的项目主页中,有一句话表明了它是遵循ECMA-262标准的:
"V8 implements ECMAScript as specified in ECMA-262."
就目前来说ECMA-262标准(曾)发布了7个大版本,如表1-4所示。
表1-4 ECMA-262标准的7个大版本
① Generator函数的爱称,因其有一个显著的标识——形如菊花的星号(*)而得名。
V8在开发的过程中也一直追着ECMAScript发布的脚步,如基本上完成了对ES6的支持,而且最新版也对async/await函数进行了支持。
也正是因为V8对ECMAScript标准紧追不舍,才有了Node.js能及时跟上ECMAScript最新语法的情况。如Node.js 7.6正式默认支持async/await功能就是沾了V8的光。
3.Node.js与Chrome V8
表1-5是V8与Node.js的部分版本对照表。
表1-5 V8与Node.js的部分版本对照表
由表1-5可见,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.
1.3.2 libuv
libuv是一个专注于异步I/O的跨平台类库。实际上它主要为Node.js而开发,不过也被其他的一些程序使用,如Luvit、Julia、pyuv等。其Logo如图1-10所示。
图1-10 libuv Logo
Node.js的一个重要概念就是事件循环。而Node.js的事件循环就是由这个libuv进行驱动的。有了libuv对Node.js的事件循环支持,读者才能在Node.js的异步世界中自由翱翔。
1.亮点特性
libuv的异步中有非常多的亮点,让开发者甚至不需要自己去管理线程等内容就能轻松地实现一套异步代码:
· 基于epoll/kqueue/IOCP/event ports实现的全能事件循环;
· 异步TCP和UDP套接字;
· 异步DNS解析;
· 异步文件、文件系统操作;
· 文件系统事件;
· ANSI转义码控制的TTY;
· 使用UNIX domain套接字或者命名管道实现的套接字共享IPC;
· 子进程;
· 线程池;
· 信号(Signal)处理;
· 高精度时钟;
· 线程和同步元。
正是不同的平台有不同的异步机制(如epoll、IOCP等),libuv才能基于它们实现跨平台的事件循环,Node.js才能尝到甜头。
2.接口简单
老实说,对于一个别人写的库,我爱不爱用主要是考察其API设计如何。也就是该怎么用,设计得好不好,有没有冗余设计。文档之类的对于我来说不太重要,反正有代码可以看嘛。
对于libuv我大体上还算满意,用C实现可以加很多分。
因为我觉得一个库,若想被人当成黑盒子去使用,以后也被当成黑盒子来维护,甚至可以用别的盒子去替代它,关键的一点就是接口简单。这个简单包括了使用最少的概念、需要最少的知识去理解它。
——节选自云风的博客中的《libuv初窥》,这也是笔者非常赞同的一点。
libuv的接口非常简单,并且明了。辅以少量的文档就能快速地上手。这是作为一个给别人使用的类库所拥有的很棒的品格。
这对本书的读者来说也是一种莫大的幸运——因为你在学习C++模块开发的时候,在libuv这一部分只需要花非常少量的时间就能上手了。
3.libuv与Node.js
libuv是Node.js最初的作者Ryan Dahl为Node.js写的底层异步库。所以可以说它天生就是为Node.js而生的,在流行起来之后也为其他很多项目提供了基础的“血液”。
Node.js的事件循环直接用的就是libuv的事件循环。在Node.js v6.9.4下,src/node.cc文件里的4690到4698行代码中我们能发现它把libuv的默认事件循环传入NodeInstanceData供其执行了。
其实不仅仅是6.9.4版本,其他版本的src/node.cc文件中也能找到对应的相关代码(即使它看起来跟上面的代码不太一样)。
1.3.3 其他依赖
本节会介绍Node.js除了Chrome V8引擎和libuv以外的一些依赖。如果读者想了解它的全部依赖,可以去翻看Node.js源码的deps目录。
1.http-parser
这是一个C实现的HTTP消息解析器,其能解析HTTP协议的请求数据和返回数据。它的项目地址在https://github.com/nodejs/http-parser,由此可见http-parser是Node.js项目抽象出来的一个第三方库,主要也是为Node.js提供相应的功能。
它的特性如下:
· 无依赖;
· 持久化连接的流式处理(keep-alive);
· 分段信息(chunk)的解码;
· 缓冲区溢出攻击的防御。
在上述特性的基础上,该依赖主要解析HTTP消息中下面的一些内容:
· 消息头键值对(Header);
· 内容长度(Content-Length);
· 请求方法(GET、POST、PUT、DELETE等);
· 返回状态码(Status Code);
· 传输编码(Transfer-Encoding);
· HTTP版本;
· 请求地址(URL);
· 消息体。
其实不仅仅是在Node.js中,如果开发者自己在开发C/C++项目时有相关需求,也可以直接引用这个库。
2.OpenSSL
OpenSSL是一套大名鼎鼎的安全套接字层协议库。
SSL就是Secure Socket Layer的缩写,其可以在网络上提供秘密性传输。Netscape公司在推出第一个Web浏览器Netscape Navigator的同时提出了SSL的标准,用于对HTTPS协议进行加密。SSL包含记录层(Record Layer)和传输层,记录层协议确定传输层数据的封装格式。传输层安全协议使用X.509认证,之后利用非对称加密演算来对通信方做身份认证,之后交换对称密钥作为会谈密钥(Session key)。这个会谈密钥用于将通信两方交换的数据做加密,保证两个应用间通信的保密性和可靠性,使客户与服务器应用之间的通信不被攻击者窃听。
OpenSSL主要由C语言编写,实现了基本的加密功能:SSL与TLS协议。同样,OpenSSL也是跨平台的。
Node.js在安全性相关层面的代码就基于OpenSSL进行了一个封装,如HTTPS协议支持。
扩展阅读:即便是OpenSSL这样一个强大的开源库,也不是完全无懈可击的。在2014年4月7日,一个关于OpenSSL的“心脏出血漏洞”(Heartbleed bug)被公开披露。该漏洞的成因是由于在实现TLS心跳扩展时没有对输入进行适当验证,导致实际可以读取的数据比允许读取的数据要多。不过在该漏洞被公开披露的当天,OpenSSL发布了修复后的版本。
3.zlib
zlib是一个老牌的年代久远的提供数据压缩功能的库,由Jean-loup Gailly和Mark Adler开发。它最初是为libpng库所写的,不过后来普遍被许多软件所使用。有趣的是,Jean-loup负责zlib的压缩逻辑而Mark Adler负责解压。其Logo如图1-11所示。
图1-11 zlib Logo
在Node.js中免不了对一些数据进行压缩、解压处理,很多时候就是用zlib的一个封装实现的。
扩展阅读:关于zlib的各种资料读者可以自行搜索,这里稍微提一下一个由Facebook开发的另一套更高效的压缩算法——Zstandard(简称zstd)。其官方主页是http://facebook.github.io/zstd/。
在其官方主页中有一张性能对比表格,其中能看出Zstandard的成绩遥遥领先于其他一些类库。Zstandard与其他压缩库的性能对比如表1-6所示。
表1-6 Zstandard与其他压缩库的性能对比
1.3.4 小结
本节主要介绍了Node.js与其他一些第三方库的不解之缘。Node.js是社区贡献者们站在巨人肩膀上所完成的一个结果。
Chrome V8为其提供了JavaScript解释和运行时的引擎,libuv为它的事件循环提供了非常好的载体。http-parser、OpenSSL、zlib等第三方库都成为Node.js的枝丫。
正是有了这些巨人,才有了现在无论是开发还是执行都效率满满的Node.js。
1.3.5 参考资料
[1]为什么V8引擎这么快?:http://blog.csdn.net/horkychen/article/details/7761199.
[2]Just-in-time compilation:https://en.wikipedia.org/wiki/Just-in-time_compilation.
[3]Javascript Hidden Classes and Inline Caching in V8:http://richardartoul.github.io/jekyll/update/2015/04/26/hidden-classes.html.
[4]ECMAScript:https://en.wikipedia.org/wiki/ECMAScript.
[5]Node 7.6 Brings Default Async/Await Support:https://www.infoq.com/news/2017/02/node-76-async-await.
[6]V8❤Node.js:https://v8project.blogspot.com/2016/12/v8-nodejs.html.
[7]Previous Releases|Node.js:https://nodejs.org/en/download/releases/.
[8]libuv初窥:http://blog.codingnow.com/2012/01/libuv.html.
[9]Transport Layer Security:https://en.wikipedia.org/wiki/Transport_Layer_Security.
[10]X.509:https://en.wikipedia.org/wiki/X.509.
[11]zlib Home Site:http://www.zlib.net/.