Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,它能够让JavaScript脚本运行在服务端,这使得JavaScript成为与PHP、Python等服务端语言平起平坐的脚本语言。
1、认识Node.js
Node.js是当今网站开发中非常流行的一种技术,它以简单易学、开发成本低、高并发等特点而深受广大开发者欢迎,本节将对Node.js的基本概念、工作原理、优缺点,以及应用领域等进行介绍。
1.1、 什么是Node.js
Node.js(简称Node)是一个开源的、基于Chrome V8引擎的服务器端JavaScript运行时环境,可以在浏览器环境以外的主机上解释和运行JavaScript代码,它发布于2009年5月,由谷歌工程师Ryan Dahl开发。Node.js支持现在大部分的主流浏览器,包括Chrome、Microsoft Edge和Opera等。Node.js主要由标准库、中间层和底层库这3部分组成,其架构如下图所示:
下面分别对上图中的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中,libuv发挥着十分重要的作用,具体如下:
- libuv使用各平台提供的事件驱动模块实现异步,这使得它可以支持Node.js应用的非文件I/O模块,并把相应的事件和回调封装成I/O观察者放到底层的事件驱动模块中。当事件触发时,libuv会执行I/O观察者中的回调。
- libuv实现了一个线程池来支持Node.js中的文件I/O、DNS、用户异步等操作。
1.2、Node.js的工作原理
1、事件驱动
Node.js采用一种独特的事件驱动思想,将I/O操作作为事件响应,而不是阻塞操作,从而实现了事件函数的快速执行与错误处理。由于Node.js能够采用异步非阻塞的方式访问文件系统、数据库、网络等外部资源,因此,它能够高效地处理海量的并发请求,极大地提高了应用程序的吞吐量。
2、单线程
Node.js采用单线程模型,只需要轻量级的线程即可处理大量的请求。与多线程模型相比,这种模型消除了线程之间的竞争,使得程序的稳定性大幅度提升。在Node.js的单线程模型中,所有的I/O操作都被放在事件队列中,一旦事件出现,Node.js就会依次处理它们。事实上,大多数网站的服务器端都不会做太多的计算,它们接收到请求以后,把请求交给其他服务来处理(如读取数据库),然后等待结果返回,再把结果发给客户端。因此,Node.js针对这一事实采用了单线程模型来处理,它不会为每个接入请求分配一个线程,而是用一个主线程处理所有的请求,然后对I/O操作进行异步处理,避开了创建、销毁线程以及在线程间切换所需的开销和复杂性。
3、非阻塞I/O
在传统的I/O操作(例如,读取或写入磁盘文件,或者对远程服务器进行网络调用)中,当数据读取或写入操作发生时,程序会被阻塞,等数据读取或写入操作完成后才能进入下一步操作。但是,在Node.js中,所有的I/O操作都是非阻塞的,当某个I/O操作发生时,不是等待其执行完成才能进入下一步操作,而是直接回调相应的函数,从而实现了对外部资源的高效访问。
4、事件循环
Node.js采用了一种特殊的设计方式—事件循环,它在工作线程池中维护一个任务队列,当接到请求后,将该请求作为一个事件放入这个队列中,然后继续接收其他请求,同时,Node.js程序会不断地从工作队列中获取要执行的事件,并通过事件循环流程对其进行处理。下图给出了Node.js中事件循环的工作原理:
事件循环的主要工作阶段如下:
- 计时器:处理由setTimeout()和setInterval()设置的回调。
- 回调:运行挂起的回调函数。
- 轮询:检索传入的I/O事件并运行与I/O相关的回调。
- 检查:完成轮询后立即运行回调。
- 关闭回调:关闭事件和回调。
注意
无论是在Linux平台还是Windows平台上,Node.js内部都是通过线程池来完成异步I/O操作的,而libuv针对不同平台的差异性实现了统一调用,因此Node.js的单线程仅仅是指JavaScript运行在单线程中,而并非Node.js是单线程的。
5、模块化设计
在Node.js中,采用了一种模块化的设计方式,按照功能模块将代码拆分成多个文件,使用require函数引入,从而提高了代码的复用率,同时也增强了代码的可维护性。另外,Node.js提供了许多内置模块,如http模块、fs模块等,能够帮助开发者快速搭建Web应用。
1.3、Node.js的优缺点
作为一种能够同时进行前端和后端开发的“年轻”编程语言,Node.js既有优点也有缺点,下面分别进行介绍。
Node.js的优点如下:
- 前后端一体化开发:Node.js使用JavaScript作为开发语言,使得前端和后端都可以使用同一种语言进行开发,从而提高开发效率和代码的可维护性。
- 丰富的模块库:Node.js的生态系统非常丰富,拥有大量的第三方模块,使得开发者可以快速构建出各种类型的应用。
- 轻量级:Node.js采用模块化开发方式,使得应用程序可以轻松地分解成小模块,从而提高了可维护性和可扩展性。
- 易部署:使用Node.js开发的应用程序可以轻松地部署到各种云端平台上。
Node.js的缺点如下:
- 缺少严格的类型检查:Node.js是基于JavaScrpt的,它没有严格的类型检查,这既是它的优点,也是它的缺点,优点是开发自由度很高,但缺点是程序出现问题时,检查调试会比较困难。
- 可靠性不如传统后端语言:由于Node.js的相对年轻和快速迭代,它在可靠性和稳定性方面,相对传统后端语言(如Java、C语言、C#等)还有一定的差距。
- CPU密集型任务表现不佳:由于Node.js的单线程模型,当需要进行大量的CPU密集型计算时,可能会出现性能瓶颈,导致程序的运行效率下降。
1.4、Node.js能做什么
使用Node.js可以生成以下类型的应用程序:
- HTTP Web服务器。
- 微服务或无服务器API后端。
- 用于数据库访问和查询的驱动程序。
- 交互式命令行接口。
- 桌面应用程序。
- 实时物联网(IoT)客户端和服务器端。
- 适用于桌面应用程序的插件。
- 用于文件处理或网络访问的Shell脚本。
- 机器学习库和模型。
1.5、谁在使用Node.js
前端最流行的JavaScript正在一步步走入后端,得益于V8引擎,Node.js为JavaScript运行在后端提供了运行环境,因此,它正在吸引越来越多的公司来使用它,比如用它创建协作工具、聊天工具、社交媒体应用程序等。
据不完全统计,现在已经有越来越多的国际和国内知名公司在内部使用了Node.js技术,如流媒体视频网站Netflix、在线支付平台PayPal、社交平台LinkedIn、Node.js专业中文社区CNode、购物平台淘宝网、腾讯官网等。