Node.js官方网站于2023年10月宣布 Node.js 21 发布。
Node.js 21 版在未来6个月将一直作为新的Current版本存在,直到2024年的4月份。
而Node.js 20 版本则变成LTS版本,进入长期维护阶段。
版本使用建议
建议如果是生产环境,则可以升级到 Node.js 20 系列版本,因为20版本是偶数版本,维护时间会比较长。
如果是学习和尝试,可以使用最新的 Node.js 21 系列版本。
如果要找老版本,可以看这个页面:nodejs.org/en/download…
fetch模块和WebStream模块已稳定
fetch模块和WebStream模块在 Node.js 21版都已经被标记为稳定。
内置WebSocket客户端
Node.js 21 版本内置了一个和浏览器中WebSocket一致的WebSocket客户端。不过还是试验性的,需要使用--experimental-websocket来开启。由于是试验状态,这个功能后续很有可能有所改变。
有了这个之后,我们就可以直接用Node.js来连接WebSocket的Server了,不再需要第三方的工具。
通过node --experimental-websocket这种方式,我们就可以使用WebSocket这个函数了,它和WebSocket标准是一致的。
举例如下:
const ws = new WebSocket('wss://websocketserver.xx/');
ws.addEventListener('message', (event) => {
// 服务端的响应消息
console.log('收到:', event.data);
});
ws.addEventListener('open', () => {
// 向服务端发送消息
ws.send(text)
});
新的V8引擎11.8版本
和以往一样,这次也内置了较新的JavaScript引擎V8 11.8版本,该版本也是Chromium 118的JS引擎。
带来的新特性如下:
a. 数组分组API:Object.groupBy。
b. ArrayBuffer.prototype.transfer
c. WebAssembly中扩展常量表达式
test runner支持并行
Node.js一直在不断的改进自动化测试模块。Node.js 21 版本引进了一个新的启动参数--test-concurrency。
通过这个参数,用户可以设置最大同时执行的测试文件数量,默认值是比处理器的核心的数量少1。
每个测试文件运行在单独的进程中。这样对于大型项目,如果机器有多个处理器核心那将可以大大提高自动化测试的运行效率。
Test Runner支持glob
举一个例子就是,node --test **/*.test.js这种glob的形式已经允许了,并且还允许制定多个glob模式:node --test **/*.test.js **/*.spec.js。
其内部实现基于npm包https://github.com/isaacs/node-glob。
--experimental-default-type启动参数
Node.js的模块系统之前默认一直是commonjs模块系统,但是现在Node.js社区想以最小的代价逐渐切换到默认使用ES模块系统,当然不能直接硬切过来,这样会让原有的程序无法运行。因此现在社区的方案是加一个参数,加上该参数后,默认模块系统就变成了ES模块系统,这样逐步进行过渡。
这个参数就是--experimental-default-type=module。一旦加上该参数将会有如下改变:
a. 任何直接送入Node.js的代码将默认是ES模块。
b. 如果没有package.json在当前文件夹或者父文件夹中时,.js后缀的文件和没有后缀的文件都会被认为是ES模块。
c. 如果有package.json,但是没有指明用那种模块,且所在目录不在node_modules下,那.js后缀的文件和没有后缀的文件都会被认为是ES模块。
当然了,你也可以指定默认使用commonjs模块:--experimental-default-type=commonjs。
另外:
如果使用了--experimental-wasm-modules参数,那任何没有后缀且包含Wasm头的文件将会被认为是Wasm模块。
模块定制钩子globalPreload被移除
这个钩子回调一直让不少开发人员困惑,它可以返回一段代码字符串,这段代码字符串将被放到一个隐式函数中和主线程中的代码一起运行。整个代码会很丑陋,而且没有必要。globalPreload在21版本直接被移除了, initialize钩子回调可以替代globalPreload,如果有代码需要在主线程执行,可以通过MessageChannel 将消息从钩子线程传给主线程。
fs.writeFile添加了flush选项
计算机中处处都有缓存存在,当我们用fs.writeFile写文件时,数据并不是直接写到文件,而是要经过各级缓存,这时如果有读文件的需求,将很有可能无法读取到最新的数据。
这次的 Node.js 21版本给fs.writeFile方法的options参数中加了一个可选项flush,当其是true时,当文件写操作完成后会立即flush所有数据到永久存储器。
它实际上是调用的fs.fsync()来实现这一功能的。
代码实例如下:
import { writeFile } from 'node:fs';
// flush默认是false
writeFile('message.txt', 'Hello Node.js', {flush: true}, callback);
stream流的性能提升
Node.js 21 的stream的read write相比于之前的版本有最高17%的性能提升。可以参考性能提升。
我们知道Node.js中到处都在用流,我们用它做网站IO或者文件处理时,都离不开流。流读写的性能提升对我们一定是有帮助的。
在新的竞争者Bun暂露头角的情况下,Node.js的性能提升是一个挺重要的保持自己优势的手段。
http的性能提升
http有一种数据传输类型Transfer-Encoding: chunked,所谓chunk就是数据是分块传输的,每个chunk块末尾都需要有\r\n标识,表示chunk块结束。
res.write()方法每次都会在后面加一个\r\n,用于分块传输的情况。
但是如果用户调用了res.cork(),将数据先放入内存中缓存起来,等时机成熟再调用res.uncork(),将数据一起返回到客户端,这时如果中间的res.write()每次都要形成一个chunk,其实完全没有必要,因为这些数据已经被合并成一块数据了。
这次的改进就是,当使用res.cork()时,res.write()不再添加\r\n等表示新chunk的东西,从而减少了数据传送量,达到性能提升的目的。
举例如下:
res.cork();
res.write('Mozilla');
res.write(' Developer Network');
res.uncork();
改进前,其输出数据:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
7\r\n
Mozilla\r\n
18\r\n
Developer Network\r\n
0\r\n
\r\n
改进后的输出数据:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
25\r\n
Mozilla Developer Network\r\n
0\r\n
\r\n
http parser强制执行严格模式
http parser是用来处理http数据的一个Node.js模块,名字叫llhttp。
在之前的版本中,llhttp默认没有开启严格模式。但是在 Node.js 21 版本中所有严格模式相关的设置均被开启。
http头后面的\r\n现在是强制的,以前可以是\r。
chunk后面的\r\n现在也是强制的。
一旦有Connection: close这样的头的消息,那就不在允许有数据传输了。
以上这些都是为了提升程序的稳定性,Node.js正在变得越来越严谨。
但是如果你的程序有些问题实在是不好修改,那可以先用这个启动参数--insecure-http-parser,这样就可以依然采用之前的数据处理策略,这个参数是为了做向后兼容。不过这个肯定是Node.js不提倡的。
和浏览器一致的navigator api
Node.js 21 新加了一个navigator对象,相信做前端开发的同学都知道这个对象。Node.js 21 部分实现了navigator,以后版本还会继续新增navigator下的其他API。
这次仅实现了navigator.hardwareConcurrency和navigator.userAgent。这两个属性在浏览器中也有。
navigator.hardwareConcurrency表示当前允许的逻辑处理的数量。
navigator.userAgent表示当前的用户代理,Node.js 21版本是Node.js/21。
Node.js 16(LTS)已经停止维护
Node.js 16(LTS)已经停止维护,强烈建议升级到 Node.js 18(LTS)或者 Node.js 20(LTS)。
结束语
其它Node.js 21更新其实还有不少,但是和用户紧密相关的就是这些了。