服务器端JavaScript框架-Node.js
2011-03-21 11:08:27 阿炯

Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台执行环境。它由 OpenJS Foundation(原为 Node.js Foundation,已与 JS Foundation 合并)持有和维护,亦为 Linux 基金会的项目。其采用 Google 开发的 V8 执行代码,使用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,可优化应用程序的传输量和规模。这些技术通常用于资料密集的即时应用程序。采用MIT许可证授权。


Node.js 大部分基本模块都用 JavaScript 语言编写。在它出现之前,JavaScript 通常作为客户端程序设计语言使用,其写出的程序常在用户的浏览器上执行。它的出现使 JavaScript 也能用于服务端编程。其内置有一系列模块使得程序可以脱离 Apache HTTP Server 或 IIS,作为独立服务器执行。其可通过JavaScript和一系列模块来编写服务器端应用和网络相关的应用。核心模块包括文件系统I/O、网络(HTTP、TCP、UDP、DNS、TLS/SSL等)、二进制数据流、加密算法、数据流等等。Node模块的API形式简单,降低了编程的复杂度。使用框架可以加速开发,常用的框架有Express.js、Socket.IO和Connect等。Node.js的程序可以在Microsoft Windows、Linux、Unix、Mac OS X等服务器上运行。其也可以使用CoffeeScript(一种旨在简化JavaScript的替代语言,其代码可按照一定规则转化为合法的JavaScript代码)、TypeScript(微软开发的强化了数据类型的JavaScript变体)、Dart语言,以及其他能够编译成JavaScript的语言编程。

其主要用于编写像Web服务器一样的网络应用,这和PHP和Perl是类似的。但是Node.js与其他语言最大的不同之处在于,PHP等语言是阻塞的(只有前一条命令执行完毕才会执行后面的命令),而Node.js是非阻塞的(多条命令可以同时被运行,通过回调函数得知命令已结束运行)。另外它也是事件驱动的,开发者可以在不使用线程的情况下开发出一个能够承载高并发的服务器。其他服务器端语言难以开发高并发应用,而且即使开发出来,性能也不尽如人意。Node.js正是在这个前提下被创造出来。它将JavaScript的易学易用和Unix网络编程的强大结合起来。

Node.js使用Google V8 JavaScript引擎,因为:
V8速度非常快且基于BSD许可证的开源软件;
V8专注于网络功能,在HTTP、DNS、TCP等方面更加成熟。

Node.js已经有数十万模块,它们可以透过一个名为npm的管理器免费下载。其竞品Bun是用Zig语言编写的JavaScript运行时,支持JavaScript和TypeScript项目的测试、编译和运行。首个对外版本于2022年7月发布。

Node.js始于2009年,原始作者是美国软件工程师瑞安·达尔(Ryan Dahl)。其结合了Google的V8、事件驱动模式和低级I/O接口,其设计灵感源自Flickr的一款上传进度栏:在上传过程中,浏览器并不清楚有多少文件已经发送到服务器,除非向服务器进行查询,于是达尔想出了一个更简便的方法。Node.js的开发和维护工作由达尔本人主持,而他所在的Joyent公司也提供了赞助。2009年11月8日,达尔在欧洲JSConf大会上展示了Node.js项目,并受到了观众赞赏。在演讲中,达尔针对Apache HTTP Server和顺序编程方式提出了批评,认为Apache处理大量并发连接(10,000甚至更多)的可能性有限,而且顺序编程方式在多连接情况下会造成阻塞,或者消耗更多资源;而Node.js提供了基于事件驱动和非阻塞的接口,可用于编写高并发状态下的程序,而且JavaScript的匿名函数、闭包、回调函数等特性就是为事件驱动而设计的。


Node.js的创始人瑞安·达尔,摄于2010年

2010年1月,一款名为“npm”的软件包管理系统诞生。其可使程序员能够更方便地发布和分享Node.js类库及源代码,而且简化了类库安装、升级与卸载的过程。其最初只支持Linux和Mac OS X操作系统。2011年6月,微软和Joyent共同合作,把Node.js移植到了Windows系统,并于同年7月发布了第一个正式支持Windows系统的版本。

2012年1月,达尔离开了Node.js项目,开发工作由他的同事以及npm创始人艾萨克·施吕特(Isaac Schlueter)继续主持。2014年2月,蒂莫西·费里斯(Timothy J. Fontaine)接任项目主管。

技术

线程
Node.js以单线程执行,使用非阻塞I/O调用,这样既可以支持数以万计的并发连线,又不会因多线程本身的特点而带来麻烦。众多请求只使用单线程的设计意味着可以用于创建高并发应用程序。Node.js应用程序的设计目标是任何需要操作I/O的函数都使用回调函数。这种设计的缺点就是如果不使用cluster、StrongLoop Process Manager或pm2等模块,Node.js就难以处理多核或多线程等情况。

V8
V8是为Google Chrome设计的JavaScript运行引擎,Google于2008年将其开源。V8用C++写成,它将JavaScript源代码编译成本地机器码而不是解释执行。Node.js用libuv来处理异步事件,而V8提供了JavaScript的实时运行环境。libuv是一个网络和文件系统功能的抽象层,既可以用于Windows又可以用于符合POSIX标准的系统,例如Linux、OS X和Unix。

Node.js的核心功能被包含进一个JavaScript库,并通过C++将各部分与操作系统进行联系。三方还有Cron库可调用。

npm
npm是Node.js附带的包管理器。npm是一个命令行工具,用于从NPM Registry中下载、安装Node.js程序,同时解决依赖问题。它提高了开发的速度,因为它能够负责第三方Node.js程序的安装与管理。

统一API
Node.js将浏览器、数据(例如MongoDB或CouchDB)等组合到一起,通过JSON提供一个统一的接口。由于前端框架和一些基本的后端开发技术(如MVC、MVP、MVVM等)变得流行,它也支持客户端和服务器端重新利用相同的模型和接口。

事件循环
Node.js将其注册到操作系统中,这样可以及时注意到新连接的产生。当新连接产生时,操作系统会产生一个回调。在其运行时内部,每个连接都被分配一个小型的堆。与其他服务器程序不同的是,Node.js不使用进程或线程处理连接,而是采用事件循环来处理并发连接。而且它在事件循环不需要手动调用。在回调函数定义之后,服务器进入事件循环。当回调函数均被执行完毕之后,Node.js结束事件循环。

Event Loop即事件循环,是指浏览器或Node.js的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理;其是一个执行模型,在不同的地方有不同的实现,浏览器和Node.js基于不同的技术实现了各自的Event Loop。

宏任务和微任务

宏任务,macrotask,也叫tasks。一些异步任务的回调会依次进入macrotask queue,还有一部分会进入其他的队列,等待后续被调用,这些异步任务包括:
setTimeout
setInterval
setImmediate(Node和最新版本IE独有)
I/O
requestAnimationFrame
requestIdleCallback

微任务,microtask, 也叫jobs。另一些异步任务的回调会依次进入microtask queue,等待后续被调用,这些异步任务包括:
process.nextTick(Node独有)
Promise.then
Object.observe
MutationObserver
(注:这里只针对浏览器和Node.js)

Event Loop运行机制

浏览器的Event Loop的执行过程:


1.执行全局script的同步代码。
2.执行微任务队列中的所有任务。
3.开始执行macrotask宏任务,从宏任务队列中取一个任务出来执行,然后又执行所有的微任务,执行的流程为:执行一个宏任务 -> 步骤2 -> 执行一个宏任务 -> 步骤2 -> 执行一个宏任务...

Node.js的Event Loop的执行过程:


1.执行全局script的同步代码。
2.执行所有microtask微任务,先执行Next Tick Queue中的所有任务,从Next Tick Queue中依次取出任务放入调用栈中执行,再执行Other Microtask Queue中的所有任务,也是从Other Microtask Queue中依次取出任务放入调用栈中执行。
3.开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一阶段macrotask中的所有任务(在浏览器的Event Loop中是每次只取宏任务队列中的一个任务出来执行,然后又执行所有的微任务),每一个阶段的macrotask执行完毕后,又开始执行所有微任务,执行的流程为:Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue ...

注意:在较新版本11.0中,Node.js为了向浏览器靠齐,对底部进行了修改,Node11及之后版本已经把在timer阶段的setTimeout,setInterval...和在check阶段的setImmediate都修改为一旦执行完一个阶段里的一个任务就立刻执行微任务队列。

NodeJS中微任务有两种,分别是process.nextTick和promise.then,那么这两个谁先执行呢?
Promise.resolve(‘123’).then(res=>{console.log(res)})
Process.nextTick(()=>{console.log(‘nextTick’)})
// 运行结果:
// nextTick
// 123

解释:
promise.then虽然和process.nextTick一样,都将回调函数注册到microtask微任务中,但优先级不一样,process.nextTick的microtask queue总是优先于promise的microtask queue执行。

setTimeout和setImmediate
在Node中,setTimeout和setImmediate执行顺序不固定 取决于Node的准备时间。
setTimeout(() => {
    console.log(‘setTimeout’)
}, 0)
setImmediate(() => {
    console.log(‘setImmediate’)
})

// 运行结果:
// setImmediate
// setTimeout
// 或者
// setTimeout
// setImmediate

解释:
setTimeout/setInterval的第二个参数取值范围是:[1, 2^31 - 1],如果超过这个范围则会初始化为1,即setTimeout(fn, 0) === setTimeout(fn, 1)。
setTimeout的回调函数在timer阶段执行,setImmediate的回调函数在check阶段执行,Event Loop开始会先检查timer阶段,但是在开始之前到timer阶段会消耗一定时间,所以就会出现两种情况:
1.timer前的准备时间超过1ms,满足loop(time >= 1),则执行timer阶段(setTimeout)的回调函数
2.timer前的准备时间小于1ms,则不满足loop(time < 1),会先执行check阶段(setImmediate)的回调函数,下一次Event Loop执行timer阶段(setTimeout)的回调函数。

1.Promise构造函数里的代码是同步执行的,Promise.then里的代码才是异步的。

2.不同环境中Event Loop的运行机制不同,所以不同环境中JS的运行结果也有可能不一致。

3.Node.js可以理解成有4个宏任务队列和2个微任务队列,但是执行宏任务时有6个阶段。


最新版本:3.2
该版本中 Node.js 将使用全新的流实现,并命名为 streams2,包括可读流增加 read() 方法用来返回缓冲区或者是 null;'data' 事件、pause() 和 resume() 方法依然可用。

最新版本:16.0
Node.js 16.0.0 已于2021年4月中旬正式发布,本次更新内容如下:
稳定 Timers Promises API:Timers Promises API 提供了一组替代的定时器函数,这些函数返回 Promise 对象。在Node.js v15.0.0 中添加,在此版本中,它们从实验状态升级为稳定状态。
Toolchain 和编译器升级:v16.0.0 将是发布用于 Apple Silicon 的预构建二进制文件的第一个版本。虽然我们将为 Intel(darwin-x64)和 ARM(darwin-arm64)架构提供单独的压缩文件,但 macOS 安装程序(.pkg)将作为多架构二进制文件提供。
更多详情可查看此处。按照此前的公告,为了配合 OpenSSL 1.1.1 系列的结束支持进度,Node.js 16 提前 7 个月结束生命周期 (EOL)。因此在 2023 年 9 月 11 日,OpenSSL 1.1.1 宣布结束生命周期后,第16版也宣布 EOL。Node.js 团队原本计划在 2024 年 4 月让 Node.js 16 EOL。但由于其在 9 月 11 日正式 EOL,考虑到在此后 7 个月内会面临因其带来的安全风险,他们决定提前结束支持。上一次这样操作的是 Node.js 8,也是为了配合 OpenSSL 1.0.2 的 EOL 时间。

最新版本:17.0
Node.js 17.0.0 正式版于2021年10月下旬发布,本次更新值得注意的变化包括:
在 http 中弃用 .aborted 属性和 abort、aborted 事件(doc-only)
移除子路径文件夹映射
运行时弃用斜杠结尾(trailing slash)模式

Node.js 现在包含 OpenSSL 3.0,特别是提供 QUIC 支持的 quictls/openssl。在 OpenSSL 3.0 中,使用新的 FIPS 模块再次提供 FIPS 支持。虽然 OpenSSL 3.0 API 与 OpenSSL 1.1.1 提供的 API 基本兼容,但由于对允许的算法和密钥大小的严格限制,预计会对它的生态产生一些影响。如果在使用 Node.js 17 的应用程序中遇到ERR_OSSL_EVP_UNSUPPORTED错误,很可能是你的应用程序或正在使用的模块尝试使用 OpenSSL 3.0 默认不再允许的算法或密钥大小。开发者可以添加--openssl-legacy-provider命令行选项 ,作为这些收紧限制的临时解决方法以恢复应用程序。V8 JavaScript 引擎已经更新到 V8 9.5。该版本为 Intl.DisplayNames API 提供了额外的支持类型,并在 Intl.DateTimeFormat API 中扩展了 timeZoneName 选项。readline 模块提供了一个接口,用于从 Readable 流(如 process.stdin)中一次一行地读取数据。

最新版本:20
Node.js 20 已正式于2023年4月中旬发布,值得关注的变化:
引入权限控制模型 (Permission Model)
Test Runner 到达稳定状态
将 V8 JavaScript 引擎升级到 11.3、Ada 升级到 2.0
支持构建单一可执行应用程序
正式支持 ARM64 Windows
要求必须指定 WASI 版本

引入权限控制模型 (Permission Model):该特性目前处于实验性阶段。Node.js 20 的权限控制功能让开发者在程序执行期间限制对特定资源的访问,例如文件系统操作、子进程生成和 worker 线程创建。通过使用此功能,开发者可以防止他们的应用程序访问或修改敏感数据,或者运行可能有害的代码。详情查看文档
自定义的 ESM loader hooks 运行在专用线程上:通过加载程序 ( --experimental-loader=foo.mjs ) 提供的 ESM hooks 现在在与主线程隔离的专用线程中运行。这为加载程序提供了一个单独的范围,并确保加载程序和应用程序代码之间没有交叉污染。
Test Runner 到达稳定状态:此次对 Node.js 20 的更新包括对 test_runner 模块的重要更改。更新之后,该模块已被标记为稳定状态。此前 test_runner 模块是实验性状态,这次更改标志着它是一个可以用于生产环境的稳定模块。
更多详情查看发行公告

最新版本:21
Node.js 21 已于2023年10月中旬正式发布。值得关注的变化包括:
将 V8 JavaScript 引擎升级至 11.8
fetch 和 WebStreams 到达稳定状态
添加用于 flip module 默认值的新实验性 flag (--experimental-default-type)
内置 WebSocket 客户端
针对 test runner 的许多更新

发行公告写道,当 Node.js 20 本月晚些时候成为长期支持版本 (LTS) 时,Node.js 21 将取代 Node.js 20 成为 "Current" 版本。根据发布时间表,Node.js 21 将在接下来的 6 个月内成为 "Current" 版本,直到 2024 年 4 月。
fetch 和 WebStreams 到达稳定状态:Node.js 21 中的 fetch 模块以及 WebStreams 模块都已被标记为稳定状态。受影响的包括 WebStreams,FormData,Headers,Request,Response 和 fetch。
内置 WebSocket 客户端:此版本中引入与浏览器兼容的实验性 WebSocket 实现。可通过该 flag 启用:--experimental-websocket。与任何实验性功能一样,它可能会发生变化。
将 V8 JavaScript 引擎升级至 11.8:Node.js 21 采用新版本 V8 引擎(更新至版本 11.8,它是 Chromium 118 的一部分),带来了改进的性能和新的语言功能,包括:
Array grouping  数组分组
ArrayBuffer.prototype.transfer
WebAssembly extended-const 表达式
最后是性能方面的变化,主要集中在改进 URL、fetch、流、node:fs 和 HTTP。更多详情可查看发行公告



官方主页:http://nodejs.org/
该文章最后由 阿炯 于 2024-03-06 11:20:43 更新,目前是第 3 版。