注册
web

一文搞清楚Node.js的本质

学习Node.js已有很长的时间了,但一直学的懵懵懂懂,不得要领,现决定跟网上的大佬从头开始理一下其中的底层逻辑,为早日成为全栈工程师打下基础。


Node.js 是什么?


Node.js 是一个基于 V8 引擎JS 运行时,它由 Ryan Dahl 在 2009 年创建。


这里有两个关键词,一是 JS 引擎,二是 JS 的运行时


那什么叫 JS 引擎呢?


image.png


JS 引擎就是把一些 JS 的代码进行解析执行,最后得到一个结果。


比如,上图的左边是用 JS 的语法定义一个变量 a 等于 1,b 等于 1,然后把 a 加 b 的值赋值给新的变量 c,接着把这个字符串传入 JS 引擎里,JS 引擎就会进行解析执行,执行完之后就可以得到对应的结果。


那么 JS 运行时又是什么呢?它和 JS 本身有什么区别 ?


要搞清楚上面的问题,可以看下面这张图:


image.png


从下往上看:




  1. 最下面一层是脚本语言规范ECMAScript,也就是常说的ES5、ES6语法。




  2. 往上一层就是对于该规范的实现了,如JavaScriptJScript等都属于对 ECMAScript语言规范的实现。




  3. 再往上一层就是执行引擎JavaScript 常见的引擎有 V8QuickJS等,用来解释执行代码。




  4. 最上面就是运行时环境了,比如基于 V8 封装的运行时环境有 ChromiumNode.jsDeno 等等。




可以看到,JavaScript 在第二层,Node.js 则在第四层,两个根本不是一个东西。


所以,Node.js 并不是语言,而是一个 JavaScript 运行时环境,它的语言是 JavaScript。这就跟 PHP、Python、Ruby 这类不一样,它们既代表语言,也可代表执行它们的运行时环境(或解释器)。


JS 作为一门语言,有独立的语法规范,提供一些内置对象和 API(如数组、对象、函数等)。但和其他语言(C、C++等)不一样的是,JS 不提供网络、文件、进程等功能,这些额外的功能是由运行时环境实现和提供的,比如浏览器或 Node.js


所以,JS 运行时可以理解为 JS 本身 + 一些拓展的能力所组成的一个运行环境,如下面这张图:


image.png


可以看到,这些运行时都不同程度地拓展了 JS 本身的功能。JS 运行时封装底层复杂的逻辑,对上层暴露 JS API,开发者只需要了解 JS 的语法,就可以使用这些 JS 运行时做很多 JS 本身无法做到的事情。


Node.js 的组成


搞清楚了什么是Node.js后,再来看看 Node.js 的组成。


Node.js 主要是由 V8Libuv 和一些第三方库组成的。


V8引擎


V8 是一个 JS 引擎,它不仅实现了 JS 解析和执行,还支持自定义拓展。


这有什么用处呢?


比如说,在下面这张图中我们直接使用了 A 函数,但 JS 本身并没有提供 A 这个函数。这个时候,我们给 V8 引擎提供的 API 里注入一个全局变量 A ,就可以直接在 JS 中使用这个 A 函数了。正是因为 V8 支持这个自定义的拓展,才有了 Node.js 等 JS 运行时


image.png


Libuv


Libuv 是一个跨平台的异步 IO 库,它主要是封装各个操作系统的一些 API,提供网络还有文件进程这些功能


我们知道在 JS 里面是没有网络文件这些功能的,前端是由浏览器提供,而 Node.js 里则是由 Libuv 提供


image.png




  1. 左侧部分是 JS 本身的功能,也就是 V8 实现的功能。




  2. 中间部分是一些C++ 胶水代码。




  3. 右侧部分是 Libuv 的代码。




V8Libuv 通过第二部分的胶水代码粘合在一起,最后就形成了整一个 Node.js


因此,在 Node.js 里面不仅可以使用 JS 本身给我们提供的一些变量,如数组、函数,还能使用 JS 本身没有提供的 TCP、文件操作和定时器功能


这些扩展出来的能力都是扩展到V8上,然后提供给开发者使用,不过,Node.js 并不是通过全局变量的方式实现扩展的,它是通过模块加载来实现的。


第三方库工具库


有了 V8 引擎和拓展 JS 能力的 Libuv,理论上就可以写一个 JS 运行时了,但是随着 JS 运行时功能的不断增加,Libuv 已经不能满足需求,比如实现加密解密、压缩解压缩。


这时候就需要使用一些经过业界验证的第三方库,比如异步 DNS 解析 c-ares 库、HTTP 解析器 llhttp、HTTP2 解析器 nghttp2、解压压缩库 zlib、加密解密库 openssl 等等。


Node.js 代码组成


了解了 Node.js 的核心组成后,再来简单看一下 Node.js 代码的组成。


image.png


Node.js 代码主要是分为三个部分,分别是 CC++JavaScript


JS


JS 代码就是我们平时使用的那些 JS 模块,像 http 和 fs 这些模块


Node.js 之所以流行,有很大一部分原因在于选择了 JS 语言。


Node.js 内核通过 CC++ 实现了核心的功能,然后通过 JS API 暴露给用户使用,这样用户只需要了解 JS 语法就可以进行开发。相比其他的语言,这个门槛降低了很多。


C++


C++代码主要分为三个部分:




  1. 第一部分主要是封装 Libuv 和第三方库的 C++ 代码,比如 netfs 这些模块都会对应一个 C++ 模块,它主要是对底层 Libuv 的一些封装。




  2. 第二部分是不依赖 Libuv 和第三方库的 C++ 代码,比方像 Buffer 模块的实现,主要依赖于 V8




  3. 第三部分 C++ 代码是 V8 本身的代码。




C++ 代码中最重要的是了解如何通过 V8 API 把 C、C++ 的能力暴露给 JS 层使用,正如前面讲到的通过拓展一个全局变量 A,然后就可以在 JS层使用 A。


C 语言层


C 语言代码主要是包括 Libuv 和第三方库的代码,它们大多数是纯 C 语言实现的代码。


Libuv 等库提供了一系列 C API,然后在 Node.jsC++ 层对其进行封装使用,最终暴露 JS APIJS 层使用。


总结


文章第一部分介绍了Node.js的本质,它实际上是一个JS运行时,提供了网络、文件、进程等功能,类似于浏览器,提供了JS的运行环境。


第二部分介绍了Node.js的组成,它由V8引擎、Libuv及第三方库构成,Node.js核心功能大都是Libuv提供的,它封装底层各个操作系统的一些 API,因此,Node.js是跨平台的。


第三部分从代码的角度描述了Node.js的组成,包括JavaScriptC++C三部分,中间的C++部分通过对C部分的封装提供给JavaScript部分使用。


作者:小p
来源:juejin.cn/post/7238814783598297144

0 个评论

要回复文章请先登录注册