一文搞清楚Node.js的本质
学习Node.js已有很长的时间了,但一直学的懵懵懂懂,不得要领,现决定跟网上的大佬从头开始理一下其中的底层逻辑,为早日成为全栈工程师打下基础。
Node.js 是什么?
Node.js 是一个基于 V8 引擎 的 JS 运行时,它由 Ryan Dahl 在 2009 年创建。
这里有两个关键词,一是 JS 引擎,二是 JS 的运行时。
那什么叫 JS 引擎呢?
JS 引擎就是把一些 JS 的代码进行解析执行,最后得到一个结果。
比如,上图的左边是用 JS 的语法定义一个变量 a 等于 1,b 等于 1,然后把 a 加 b 的值赋值给新的变量 c,接着把这个字符串传入 JS 引擎里,JS 引擎就会进行解析执行,执行完之后就可以得到对应的结果。
那么 JS 运行时又是什么呢?它和 JS 本身有什么区别 ?
要搞清楚上面的问题,可以看下面这张图:
从下往上看:
最下面一层是脚本语言规范
ECMAScript,也就是常说的ES5、ES6语法。往上一层就是对于该规范的实现了,如
JavaScript、JScript等都属于对ECMAScript语言规范的实现。再往上一层就是执行引擎,
JavaScript常见的引擎有V8、QuickJS等,用来解释执行代码。最上面就是运行时环境了,比如基于 V8 封装的运行时环境有
Chromium、Node.js、Deno等等。
可以看到,JavaScript 在第二层,Node.js 则在第四层,两个根本不是一个东西。
所以,Node.js 并不是语言,而是一个 JavaScript 运行时环境,它的语言是 JavaScript。这就跟 PHP、Python、Ruby 这类不一样,它们既代表语言,也可代表执行它们的运行时环境(或解释器)。
JS 作为一门语言,有独立的语法规范,提供一些内置对象和 API(如数组、对象、函数等)。但和其他语言(C、C++等)不一样的是,JS 不提供网络、文件、进程等功能,这些额外的功能是由运行时环境实现和提供的,比如浏览器或 Node.js。
所以,JS 运行时可以理解为 JS 本身 + 一些拓展的能力所组成的一个运行环境,如下面这张图:
可以看到,这些运行时都不同程度地拓展了 JS 本身的功能。JS 运行时封装底层复杂的逻辑,对上层暴露 JS API,开发者只需要了解 JS 的语法,就可以使用这些 JS 运行时做很多 JS 本身无法做到的事情。
Node.js 的组成
搞清楚了什么是Node.js后,再来看看 Node.js 的组成。
Node.js 主要是由 V8、Libuv 和一些第三方库组成的。
V8引擎
V8 是一个 JS 引擎,它不仅实现了 JS 解析和执行,还支持自定义拓展。
这有什么用处呢?
比如说,在下面这张图中我们直接使用了 A 函数,但 JS 本身并没有提供 A 这个函数。这个时候,我们给 V8 引擎提供的 API 里注入一个全局变量 A ,就可以直接在 JS 中使用这个 A 函数了。正是因为 V8 支持这个自定义的拓展,才有了 Node.js 等 JS 运行时。
Libuv
Libuv 是一个跨平台的异步 IO 库,它主要是封装各个操作系统的一些 API,提供网络还有文件进程这些功能。
我们知道在 JS 里面是没有网络文件这些功能的,前端是由浏览器提供,而 Node.js 里则是由 Libuv 提供。
左侧部分是 JS 本身的功能,也就是 V8 实现的功能。
中间部分是一些C++ 胶水代码。
右侧部分是
Libuv的代码。
V8 和 Libuv 通过第二部分的胶水代码粘合在一起,最后就形成了整一个 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 代码的组成。
Node.js 代码主要是分为三个部分,分别是 C、C++ 和 JavaScript。
JS层
JS 代码就是我们平时使用的那些 JS 模块,像 http 和 fs 这些模块。
Node.js 之所以流行,有很大一部分原因在于选择了 JS 语言。
Node.js 内核通过 C、C++ 实现了核心的功能,然后通过 JS API 暴露给用户使用,这样用户只需要了解 JS 语法就可以进行开发。相比其他的语言,这个门槛降低了很多。
C++层
C++代码主要分为三个部分:
第一部分主要是封装
Libuv和第三方库的C++代码,比如net和fs这些模块都会对应一个C++模块,它主要是对底层Libuv的一些封装。第二部分是不依赖
Libuv和第三方库的C++代码,比方像Buffer模块的实现,主要依赖于V8。第三部分
C++代码是V8本身的代码。
C++ 代码中最重要的是了解如何通过 V8 API 把 C、C++ 的能力暴露给 JS 层使用,正如前面讲到的通过拓展一个全局变量 A,然后就可以在 JS层使用 A。
C 语言层
C 语言代码主要是包括 Libuv 和第三方库的代码,它们大多数是纯 C 语言实现的代码。
Libuv 等库提供了一系列 C API,然后在 Node.js 的 C++ 层对其进行封装使用,最终暴露 JS API 给 JS 层使用。
总结
文章第一部分介绍了Node.js的本质,它实际上是一个JS运行时,提供了网络、文件、进程等功能,类似于浏览器,提供了JS的运行环境。
第二部分介绍了Node.js的组成,它由V8引擎、Libuv及第三方库构成,Node.js核心功能大都是Libuv提供的,它封装底层各个操作系统的一些 API,因此,Node.js是跨平台的。
第三部分从代码的角度描述了Node.js的组成,包括JavaScript、C++、C三部分,中间的C++部分通过对C部分的封装提供给JavaScript部分使用。
来源:juejin.cn/post/7238814783598297144