认识 Node.js
Node.js 是当今网站开发中非常流行的一种技术,它以简单易学、开发成本低、高并发等特点而深受广大开发者欢迎,本节将对 Node.js 的基本概念、工作原理、优缺点,以及应用领域等进行介绍。
什么是 Node.js
Node.js(简称 Node)是一个开源的、基于 Chrome V8 引擎的服务器端 JavaScript 运行时环境,可以在浏览器环境以外的主机上解释和运行 JavaScript 代码,它发布于 2009 年 5 月,由谷歌工程师 Ryan Dahl 开发。Node.js 支持现在大部分的主流浏览器,包括 Chrome、Microsoft Edge 和 Opera 等。Node.js 主要由标准库、中间层和底层库这 3 部分组成,其架构如图1.1所示。

下面分别对图 1.1 中的 Node.js 结构层进行介绍。
-
标准库(Node standard library):提供了开发人员能够直接进行调用并使用的一些 API,如
http
模块、stream
流模块、fs
文件系统模块等,可以使用 JavaScript 代码直接调用。 -
中间层(Node binding):由于 Node.js 的底层库采用 C/C++ 实现,而标准库中的 JavaScript 代码无法直接与 C/C++ 进行通信,因此提供了中间层,它在标准库和底层库之间起到了桥梁的作用,它封装了底层库中 V8 引擎和
libuv
等的实现细节,并向标准库提供基础 API 服务。 -
底层库(C/C++ 实现):底层库是 Node.js 运行的关键,它由 C/C++ 实现,包括 V8 引擎、
libuv
、C-ares
、OpenSSL
、zlib
等,它们的主要作用如下。-
V8 引擎:Google 的一个开源的 JavaScript 和 WebAssembly 引擎,使用 C++ 语言编写,用于 Chrome 浏览器和 Node.js 等。V8 引擎主要是为了提高 JavaScript 的运行效率,因此它采用了提前编译的方式,将 JavaScript 编译为原生机器码,这样在执行阶段程序的执行效率可以完全媲美二进制程序。
-
libuv:一个专门为 Node.js 量身打造的跨平台异步
I/O
库,使用 C 语言编写,提供了非阻塞的文件系统、DNS
、网络、子进程、管道、信号、轮询和流式处理机制。Node.js 会通过中间层将用户的 JavaScript 代码传递给底层库的 V8 引擎进行解析,然后通过libuv
进行循环调度,最后再返回给调用 Node.js 标准库的应用。 -
C-ares:一个用来处理异步
DNS
请求的库,使用 C 语言编写,对应 Node.js 中dns
模块提供的resolve()
系列方法。 -
OpenSSL:一个通用的加密库,通常用于网络传输中的
TLS
和SSL
协议实现,对应 Node.js 中的tls
、crypto
模块。 -
zlib:一个提供压缩和解压支持的底层模块。
-
在 Node.js 中,
|
Node.js 的工作原理
通过上一节的讲解,我们了解了 Node.js 的基本技术架构,本节进一步讲解 Node.js 的工作原理。
事件驱动
Node.js 采用一种独特的事件驱动思想,将 I/O
操作作为事件响应,而不是阻塞操作,从而实现了事件函数的快速执行与错误处理。由于 Node.js 能够采用异步非阻塞的方式访问文件系统、数据库、网络等外部资源,因此,它能够高效地处理海量的并发请求,极大地提高了应用程序的吞吐量。
单线程
Node.js 采用单线程模型,只需要轻量级的线程即可处理大量的请求。与多线程模型相比,这种模型消除了线程之间的竞争,使得程序的稳定性大幅度提升。在 Node.js 的单线程模型中,所有的 I/O
操作都被放在事件队列中,一旦事件出现,Node.js 就会依次处理它们。事实上,大多数网站的服务器端都不会做太多的计算,它们接收到请求以后,把请求交给其他服务来处理(如读取数据库),然后等待结果返回,再把结果发给客户端。因此,Node.js 针对这一事实采用了单线程模型来处理,它不会为每个接入请求分配一个线程,而是用一个主线程处理所有的请求,然后对 I/O
操作进行异步处理,避开了创建、销毁线程以及在线程间切换所需的开销和复杂性。
非阻塞I/O
在传统的 I/O
操作(例如,读取或写入磁盘文件,或者对远程服务器进行网络调用)中,当数据读取或写入操作发生时,程序会被阻塞,等数据读取或写入操作完成后才能进入下一步操作。但是,在 Node.js 中,所有的 I/O
操作都是非阻塞的,当某个 I/O
操作发生时,不是等待其执行完成才能进入下一步操作,而是直接回调相应的函数,从而实现了对外部资源的高效访问。
事件循环
Node.js 采用了一种特殊的设计方式—事件循环,它在工作线程池中维护一个任务队列,当接到请求后,将该请求作为一个事件放入这个队列中,然后继续接收其他请求,同时,Node.js 程序会不断地从工作队列中获取要执行的事件,并通过事件循环流程对其进行处理。图1.2给出了 Node.js 中事件循环的工作原理。

事件循环的主要工作阶段如下。
-
计时器:处理由
setTimeout()
和setInterval()
设置的回调。 -
回调:运行挂起的回调函数。
-
轮询:检索传入的
I/O
事件并运行与I/O
相关的回调。 -
检查:完成轮询后立即运行回调。
-
关闭回调:关闭事件和回调。
无论是在 Linux 平台还是 Windows 平台上,Node.js 内部都是通过线程池来完成异步 |
Node.js 的优缺点
作为一种能够同时进行前端和后端开发的 “年轻” 编程语言,Node.js 既有优点也有缺点,下面分别进行介绍。
Node.js 的优点如下。
-
前后端一体化开发:Node.js 使用 JavaScript 作为开发语言,使得前端和后端都可以使用同一种语言进行开发,从而提高开发效率和代码的可维护性。
-
丰富的模块库:Node.js 的生态系统非常丰富,拥有大量的第三方模块,使得开发者可以快速构建出各种类型的应用。
-
轻量级:Node.js 采用模块化开发方式,使得应用程序可以轻松地分解成小模块,从而提高了可维护性和可扩展性。
-
易部署:使用 Node.js 开发的应用程序可以轻松地部署到各种云端平台上。
Node.js 的缺点如下。
-
缺少严格的类型检查:Node.js 是基于 JavaScript 的,它没有严格的类型检查,这既是它的优点,也是它的缺点,优点是开发自由度很高,但缺点是程序出现问题时,检查调试会比较困难。
-
可靠性不如传统后端语言:由于 Node.js 的相对年轻和快速迭代,它在可靠性和稳定性方面,相对传统后端语言(如Java、C语言、C#等)还有一定的差距。
-
CPU 密集型任务表现不佳:由于 Node.js 的单线程模型,当需要进行大量的 CPU 密集型计算时,可能会出现性能瓶颈,导致程序的运行效率下降。