技术干货 # nodejs # 后端已发布

Node.js 18 到 Node.js 26:八年间那些真正值得关注的变化

如果你一直在使用 Node.js,可能会有一种很明显的感觉 Node.js 已经越来越不像一个“只能跑 JavaScript 的后端运行时”了。Node.js 18 时代,我们开始习惯直接使用 `fetch()`、`node:test`;到了 Node.js 20,Node.js 开始拥有 Permission Model;Node.js 22 进一步完善 ESM、WebSocket、`node --run`、文件系统 `glob`...

2026.10.0513 分钟阅读
Node.js 18 到 Node.js 26:八年间那些真正值得关注的变化
Node.js 18 到 Node.js 26:八年间那些真正值得关注的变化 · Cover

如果你一直在使用 Node.js,可能会有一种很明显的感觉:

Node.js 已经越来越不像一个“只能跑 JavaScript 的后端运行时”了。

Node.js 18 时代,我们开始习惯直接使用 fetch()、node:test;

到了 Node.js 20,Node.js 开始拥有 Permission Model;

Node.js 22 进一步完善 ESM、WebSocket、node --run、文件系统 glob;

Node.js 24 开始把 URLPattern、Float16Array、RegExp.escape()、显式资源管理等现代能力带进运行时;

而 Node.js 26 最值得关注的事情之一,是 Temporal 正式默认开启。

更有意思的是,这些变化并不只是“JavaScript 语法变多了”。

Node.js 本身正在逐渐吸收浏览器标准、Web API、现代模块系统、测试工具、权限控制、开发工具甚至数据库能力。

本文不追求罗列所有 ChangeLog,而是从实际开发的角度,看看 Node.js 18 → 26 到底有哪些值得关注的变化。


先看版本路线

Node.js 使用偶数版本作为主要 LTS 版本线。

截至 2026 年 10 月,目前版本状态大致如下:

Node.js发布时间状态代表性变化
Node 182022-04EOLfetch、node:test
Node 202023-04EOLPermission Model、稳定 Test Runner
Node 222024-04LTSWebSocket、node --watch、node --run、require(ESM)
Node 242025-05LTSURLPattern、Float16Array、RegExp.escape、Permission Model 改进
Node 262026-05CurrentTemporal、V8 14.6、Undici 8

Node.js 官方目前建议生产环境使用 Active LTS 或 Maintenance LTS 版本。

所以如果你现在新建一个项目:

Node 18 ❌ 不建议
Node 20 ❌ 已 EOL
Node 22 ✅
Node 24 ⭐ 当前比较推荐
Node 26 🧪 适合尝鲜和验证新特性

如果是企业项目,实际选择还需要考虑 NestJS、TypeORM、原生模块以及各种三方依赖的兼容性。


Node.js 18:Node.js 开始拥有真正的 Web API

Node.js 18 是一个非常重要的版本。

因为从这个版本开始,Node.js 和浏览器之间的 API 差异开始明显缩小。

1. fetch 正式进入 Node.js

以前 Node.js 写 HTTP 请求,经常需要:

npm install axios

或者:

npm install node-fetch

然后:

const axios = require('axios');

const response = await axios.get('https://example.com');

Node.js 18 开始提供全局 fetch:

const response = await fetch('https://example.com');

const data = await response.json();

console.log(data);

而且这些 Web API 也逐渐成为 Node.js 的标准能力:

fetch
Request
Response
Headers
FormData
Blob

Node.js 18 的 fetch 基于 Undici 实现。

这件事情的重要性其实比看起来大。

因为以前:

浏览器
   ↓
fetch

Node.js
   ↓
axios / node-fetch / request

逐渐变成:

浏览器
   ↓
fetch

Node.js
   ↓
fetch

这让前后端共享代码变得更加容易。


2. Web Streams 开始进入 Node.js

Node.js 本身一直有:

Readable
Writable
Duplex
Transform

但随着 Web API 的进入,Node.js 开始支持:

ReadableStream
WritableStream
TransformStream

这对于处理:

  • HTTP Streaming
  • SSE
  • AI 流式输出
  • 文件流
  • Web API

越来越重要。

尤其是现在做 AI 应用:

浏览器
   ↓
SSE / Stream
   ↓
Node.js
   ↓
LLM

Web Streams 的存在让浏览器和 Node.js 的流处理模型越来越接近。


3. node:test:Node.js 开始自己提供测试框架

Node.js 18 还带来了一个很有意思的东西:

import test from 'node:test';
import assert from 'node:assert';

test('addition', () => {
  assert.strictEqual(1 + 1, 2);
});

执行:

node --test

这意味着 Node.js 本身开始提供原生测试能力。

以前我们通常会想到:

Jest
Mocha
Vitest
AVA

现在 Node.js 自己就有:

node:test

Node.js 20 又进一步把 Test Runner 标记为稳定。

当然,这并不意味着 Jest、Vitest 就没有意义。

对于大型项目:

Vitest / Jest
    ↓
完整测试生态

node:test
    ↓
Node.js 原生测试能力

两者依然可以长期共存。


Node.js 20:开始从“运行时”走向“平台”

Node.js 20 是另一个非常重要的版本。

它最大的特点之一不是某一个 API,而是:

Node.js 开始尝试解决“运行一个 JavaScript 程序到底应该拥有多少权限”这个问题。


4. Permission Model

Node.js 20 引入了实验性的 Permission Model。

例如:

node \
  --experimental-permission \
  --allow-fs-read=/app \
  --allow-fs-write=/tmp \
  app.js

这样程序访问文件系统的能力就可以受到限制。

代码:

import fs from 'node:fs';

fs.readFileSync('/etc/passwd');

可能因为没有权限而失败。

Node.js 还提供:

process.permission.has('fs.read');

用于判断当前进程是否拥有对应权限。

Node.js 20 首次引入了这个机制,Node.js 24 又进一步将启动参数从:

--experimental-permission

改进为:

--permission

说明这项能力正在逐渐成熟。

对于普通 Web API 来说,这个功能可能暂时不是刚需。

但是如果你运行:

插件系统
脚本执行平台
AI Agent
Serverless
任务调度器
第三方代码

Permission Model 就非常值得关注。

例如一个 AI Agent:

AI Agent
   ↓
Node.js
   ├── 可以访问 /tmp
   ├── 可以访问项目目录
   ├── ❌ 不能访问 /etc
   ├── ❌ 不能启动任意进程
   └── ❌ 不能访问其他敏感目录

这比单纯依赖 Docker 隔离多了一层运行时控制。


Node.js 20:Test Runner 终于成熟

Node.js 18 的:

node:test

到了 Node.js 20 已经稳定。

例如:

import { test, describe } from 'node:test';
import assert from 'node:assert';

describe('UserService', () => {
  test('should create user', () => {
    const user = {
      name: 'Tom'
    };

    assert.strictEqual(user.name, 'Tom');
  });
});

然后:

node --test

甚至可以:

node --test --watch

Node.js 20 的 Test Runner 已经支持测试组织、mock、watch mode,以及并行运行测试文件等能力。

对于一些小型 Node.js 项目来说:

Node.js
+
node:test
+
node:assert

已经可以完成相当一部分测试工作。


Node.js 22:现代 Node.js 的分水岭

如果说:

Node 18 = Web API 开始进入 Node.js

Node 20 = Node.js 开始完善平台能力

Node 22 = Node.js 开始真正现代化

我认为这个评价并不过分。

Node.js 22 带来了几个非常值得开发者关注的变化。


5. WebSocket 进入 Node.js

以前 Node.js 写 WebSocket:

npm install ws

然后:

import WebSocket from 'ws';

const ws = new WebSocket('wss://example.com');

ws.on('message', message => {
  console.log(message);
});

Node.js 22 开始默认提供 WebSocket Client:

const socket = new WebSocket('wss://example.com');

socket.addEventListener('open', () => {
  console.log('connected');
});

socket.addEventListener('message', event => {
  console.log(event.data);
});

Node.js 22 的 WebSocket client 默认启用。

于是 Node.js 的 Web API 越来越完整:

fetch
WebSocket
Request
Response
Headers
FormData
Blob
ReadableStream
URL
URLPattern

Node.js 和浏览器之间的边界越来越模糊。


6. node --watch

以前 Node.js 开发经常依赖:

nodemon

例如:

nodemon src/main.js

Node.js 22 将 --watch 标记为稳定:

node --watch src/main.js

文件变化后自动重新启动。

对于简单项目:

Node.js
   +
node --watch

已经可以替代:

nodemon

的一部分使用场景。

这也是一个非常典型的趋势:

Node.js 正在不断把以前需要 npm 安装的开发工具,逐渐变成自己的内置能力。


7. node --run

这个功能可能没有 fetch 那么出名,但实际非常方便。

假设:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint ."
  }
}

以前:

npm run build

Node.js 22 开始可以:

node --run build

Node.js 会直接读取:

package.json

中的 scripts。

Node.js 22 官方将:

node --run

作为新的命令行能力加入。

它的意义并不是“以后不用 npm 了”。

而是 Node.js CLI 本身开始理解现代 JavaScript 项目的常见工作流。


8. require() 开始可以加载部分 ESM

这是 Node.js 22 非常值得关注的一件事情。

过去 Node.js 的世界经常是:

CommonJS
require()

和:

ESM
import

两个世界互相折磨。

例如:

const xxx = require('xxx');

遇到:

Error [ERR_REQUIRE_ESM]

相信很多维护老项目的人都见过。

Node.js 22 开始支持从 CommonJS 中加载满足条件的同步 ESM graph。

例如:

const module = require('./module.mjs');

不过有一个非常重要的限制:

被 require() 加载的 ESM 不能包含顶层 await。

当前 Node.js 文档显示,这项能力从 Node.js 22 开始加入,并在后续版本逐渐稳定。

所以 Node.js 现在正在逐渐走向:

CommonJS ───────────┐
                    ├── Node.js
ESM ────────────────┘

而不是过去:

CommonJS
    ↕
各种 ERR_REQUIRE_ESM
    ↕
ESM

9. fs.glob():终于不用每个项目装 glob 库了

Node.js 22 开始提供:

import { glob } from 'node:fs/promises';

const files = await glob('src/**/*.ts');

for await (const file of files) {
  console.log(file);
}

以前通常需要:

npm install glob

然后:

import { glob } from 'glob';

现在 Node.js 自己开始提供 glob 能力。

这种变化看起来很小。

但是从 Node.js 的长期发展来看非常明显:

以前:

Node.js
 +
npm 生态
 +
几十个基础工具库

现在:

Node.js
 ├── fetch
 ├── WebSocket
 ├── test
 ├── watch
 ├── glob
 ├── run
 └── 更多 Web API

Node.js 的“标准库”正在变得越来越现代。


Node.js 24:开始进入真正的现代 JavaScript Runtime

Node.js 24 于 2025 年 5 月发布。

V8 升级到 13.6,同时带来了:

Float16Array
RegExp.escape
Error.isError
显式资源管理
WebAssembly Memory64

并且 Node.js 24 自带 npm 11。


10. RegExp.escape()

这个功能非常实用。

以前如果你需要把用户输入安全地放进正则表达式:

const keyword = 'a.b*c';

const regex = new RegExp(keyword);

就可能出现问题。

因为:

.
*
+
?
[
]
(
)

这些字符在正则表达式中都有特殊含义。

以前我们经常需要自己写:

function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

Node.js 24 对应的现代 JavaScript 环境已经支持:

const regex = new RegExp(
  RegExp.escape(keyword)
);

例如:

const keyword = 'a.b*c';

const regex = new RegExp(
  RegExp.escape(keyword)
);

这类 API 很适合:

搜索
日志查询
动态过滤
用户输入
规则匹配

11. Float16Array

Node.js 24 的 V8 13.6 引入了:

Float16Array

以前 TypedArray 主要是:

Int8Array
Uint8Array

Int16Array
Uint16Array

Int32Array
Uint32Array

Float32Array
Float64Array

现在增加:

Float16Array

对于普通后端开发来说可能没什么感觉。

但对于:

AI
机器学习
GPU
WebGPU
图像处理
音视频
模型推理

半精度浮点数非常重要。

例如:

const values = new Float16Array([
  1.2,
  2.4,
  3.6
]);

它可以在一些场景下减少数据占用。

这也说明 Node.js 的发展方向已经不只是:

Web 后端。

而是在逐渐向:

通用 JavaScript Runtime

发展。


12. URLPattern

Node.js 24 将:

URLPattern

放到了全局环境。

例如:

const pattern = new URLPattern({
  pathname: '/users/:id'
});

const result = pattern.exec(
  'https://example.com/users/123'
);

console.log(result.pathname.groups.id);

得到:

123

这对于:

路由
URL 匹配
Middleware
Edge Runtime
Web Framework

都比较有意义。

Node.js 24 官方也将 URLPattern 列为全局 API。


13. AsyncLocalStorage 越来越重要

Node.js 后端开发中,一个非常重要但容易被忽略的能力是:

AsyncLocalStorage

它可以让你在异步调用链中保存上下文。

例如:

HTTP Request
    ↓
Controller
    ↓
Service
    ↓
Repository
    ↓
Database

我们希望整个链路都能拿到:

requestId
userId
traceId
tenantId

例如:

const storage = new AsyncLocalStorage();

storage.run(
  {
    requestId: 'abc-123'
  },
  () => {
    service();
  }
);

在后面的异步代码中:

const context = storage.getStore();

console.log(context.requestId);

Node.js 24 开始默认使用 AsyncContextFrame 实现 AsyncLocalStorage,这也是 Node.js 在异步上下文性能方面持续优化的一个方向。

对于:

NestJS
日志
链路追踪
OpenTelemetry
微服务
请求上下文

这个能力非常值得掌握。


Node.js 26:Temporal 终于来了

如果让我从 Node.js 18 → 26 里面选一个最值得普通 JavaScript 开发者学习的新 API:

我会选 Temporal。

Node.js 26 于 2026 年 5 月发布,Temporal API 默认开启。


14. Temporal:终于可以认真处理日期和时间了

JavaScript 老牌时间 API:

new Date()

大家用了很多年。

但 Date 有很多历史遗留问题。

比如:

const date = new Date();

console.log(date);

它同时承担了:

日期
时间
时间戳
时区转换
本地时间
UTC

导致处理复杂业务时非常痛苦。

例如:

2026-10-08

到底是:

UTC

还是:

Asia/Shanghai

还是:

Asia/Tokyo

?

Temporal 的设计就是为了把这些概念拆开。


Temporal.PlainDate

如果只需要日期:

const date = Temporal.PlainDate.from(
  '2026-10-08'
);

console.log(date);

得到的是:

2026-10-08

它不携带时间,也不携带时区。

非常适合:

生日
节假日
账单日
合同日期
活动日期

Temporal.PlainTime

如果只需要时间:

const time = Temporal.PlainTime.from(
  '14:30:00'
);

得到:

14:30:00

Temporal.ZonedDateTime

如果需要真正处理:

日期
+
时间
+
时区

可以使用:

const dateTime =
  Temporal.ZonedDateTime.from(
    '2026-10-08T14:30:00+08:00[Asia/Shanghai]'
  );

这对于:

国际化
预约系统
日历
航班
金融
支付
日志
定时任务

非常重要。


15. Node.js 26 还带来了 Iterator.concat()

Node.js 26 的 V8 14.6 包含:

Iterator.concat()

例如:

const a = [1, 2, 3];
const b = [4, 5, 6];

const result = Iterator.concat(a, b);

console.log([...result]);

得到:

[1, 2, 3, 4, 5, 6]

它和数组:

[...a, ...b]

看起来差不多。

但 Iterator 的优势在于:

可以保持惰性。

对于大量数据:

数据库 Cursor
       ↓
Iterator
       ↓
过滤
       ↓
转换
       ↓
输出

可以避免一次性把所有数据全部加载到内存。

对于 Node.js 后端的数据处理、日志处理、流处理,这类能力会越来越重要。

Node.js 26 官方已经将 Iterator.concat() 列入 V8 14.6 带来的新能力。


16. Map / WeakMap 的 getOrInsert

Node.js 26 还加入了:

Map.prototype.getOrInsert()

以前我们可能会写:

let value = map.get(key);

if (value === undefined) {
  value = createValue();

  map.set(key, value);
}

现在可以:

const value = map.getOrInsert(
  key,
  createValue()
);

还有:

getOrInsertComputed()

可以根据 key 计算默认值。

这种 API 对:

缓存
Memoization
分组
索引
状态管理

非常方便。


17. Node.js 26 开始正式进入 Temporal 时代

如果把 Node.js 18 到 26 的变化压缩一下:

Node.js 18
    ↓
fetch / Web Streams / Test Runner

Node.js 20
    ↓
Permission Model / Stable Test Runner

Node.js 22
    ↓
WebSocket / watch / run / ESM / glob

Node.js 24
    ↓
URLPattern / Float16Array / RegExp.escape
/ AsyncContextFrame

Node.js 26
    ↓
Temporal / Iterator.concat
/ Map.getOrInsert

你会发现一个非常明显的趋势:

Node.js 正在不断减少“为了完成基础工作而安装第三方 npm 包”的需求。


Node.js 最大的变化,其实不是 API

如果只看 API,很容易把 Node.js 18 → 26 理解成:

多了几个函数

但实际上变化更深。

Node.js 正在发生三个方向的变化。


第一:Node.js 和浏览器越来越像

以前:

浏览器
 ├── fetch
 ├── WebSocket
 ├── Web Streams
 └── Web API

Node.js
 ├── http
 ├── https
 ├── stream
 └── 各种 npm package

现在:

浏览器
 ├── fetch
 ├── WebSocket
 ├── Streams
 └── Web API

Node.js
 ├── fetch
 ├── WebSocket
 ├── Streams
 ├── URLPattern
 ├── Web Crypto
 └── 更多 Web API

这意味着未来前后端共享代码会越来越自然。


第二:Node.js 正在吸收以前属于 npm 生态的能力

以前:

HTTP
→ axios

文件匹配
→ glob

开发自动重启
→ nodemon

测试
→ Jest / Mocha

WebSocket
→ ws

现在逐渐变成:

HTTP
→ fetch

文件匹配
→ fs.glob

开发自动重启
→ node --watch

测试
→ node:test

WebSocket
→ WebSocket

当然,这并不是说:

npm 包没用了。

大型项目依然需要:

NestJS
Fastify
Express
TypeORM
Prisma
Zod
Pino
Vitest
...

但 Node.js 自身提供的基础能力越来越丰富。


第三:Node.js 正在变成一个完整的平台

Node.js 早期的定位非常简单:

JavaScript
+
V8
+
事件循环
+
网络 IO

现在已经逐渐变成:

                         Node.js

 ┌─────────────────────────────────────────┐
 │ JavaScript / V8                         │
 ├─────────────────────────────────────────┤
 │ Web APIs                                │
 │ fetch / WebSocket / Streams / Crypto    │
 ├─────────────────────────────────────────┤
 │ Module System                           │
 │ CommonJS / ESM                          │
 ├─────────────────────────────────────────┤
 │ Development                             │
 │ test / watch / run                      │
 ├─────────────────────────────────────────┤
 │ Security                                │
 │ Permission Model                        │
 ├─────────────────────────────────────────┤
 │ System                                  │
 │ fs / worker / child_process              │
 ├─────────────────────────────────────────┤
 │ Modern JavaScript                       │
 │ Temporal / Iterator / TypedArray         │
 └─────────────────────────────────────────┘

这也是为什么现在学习 Node.js,已经不能只理解成:

“会不会写 Express?”

而应该逐渐理解:

Node.js Runtime 本身提供了什么能力。


Node.js 18 → 26,哪些值得优先学?

如果你是普通 Node.js 后端开发者,我不建议按照版本号一个一个背。

更推荐按照实际价值学习。

第一梯队:现在就应该会

fetch
async/await
AbortController
Web Streams
ESM
node:test
AsyncLocalStorage
node --watch

这些已经和现代 Node.js 开发高度相关。


第二梯队:非常值得掌握

WebSocket
fs.glob
node --run
Permission Model
URLPattern
RegExp.escape
structuredClone
Web Crypto

尤其是:

AsyncLocalStorage
AbortController
Web Streams

对于后端开发非常实用。


第三梯队:未来几年值得关注

Temporal
Iterator
Float16Array
WebAssembly
Explicit Resource Management
Map.getOrInsert()

尤其:

Temporal

值得提前学习。

因为日期时间是后端系统里永远绕不开的问题。


如果你正在维护老 Node.js 项目

这部分可能比新 API 更重要。

如果你的项目还是:

Node 14
Node 16
Node 18

升级时千万不要简单理解成:

nvm install 24

然后:

pnpm install

就结束了。

Node.js 大版本升级真正容易出问题的是:

Node.js
  ↓
V8
  ↓
OpenSSL
  ↓
npm / Corepack
  ↓
原生模块
  ↓
TypeScript
  ↓
依赖包

尤其是老项目。

例如:

nodejieba
sharp
bcrypt
grpc
canvas
sqlite3

这类涉及:

C/C++
Native Addon
node-gyp
预编译二进制

的依赖,非常容易因为 Node.js 大版本升级而出问题。


Node.js 版本升级应该怎么做?

建议按照这个顺序:

1. 确认 Node.js 目标版本
        ↓
2. 检查 package.json engines
        ↓
3. 检查原生依赖
        ↓
4. 更新 npm / pnpm / yarn
        ↓
5. 删除 node_modules
        ↓
6. 重新安装依赖
        ↓
7. TypeScript 编译
        ↓
8. 单元测试
        ↓
9. 集成测试
        ↓
10. Docker 构建
        ↓
11. 生产环境验证

尤其不要在老项目里直接:

rm -rf node_modules
npm install

然后看到一堆错误就认为:

Node.js 坏了。

很多时候实际上是:

Node.js 升级
    ↓
V8 / OpenSSL / ABI / npm 变化
    ↓
旧依赖暴露兼容性问题

Node.js 18 到 26,最值得记住的几个时间点

如果不想记一大堆版本号,只需要记住:

Node 18
    ↓
fetch
node:test

Node 20
    ↓
Permission Model
稳定 Test Runner

Node 22
    ↓
WebSocket
node --watch
node --run
require(ESM)
fs.glob

Node 24
    ↓
URLPattern
Float16Array
RegExp.escape
AsyncContextFrame

Node 26
    ↓
Temporal
Iterator.concat
Map.getOrInsert

这张表基本可以代表过去几年 Node.js 的发展方向。


最后

如果把 Node.js 18 → 26 看成一次升级,不如把它理解成一次 Node.js 定位的变化。

早期的 Node.js 更像:

JavaScript Runtime

现在越来越像:

现代 Web Runtime
+
JavaScript Runtime
+
开发工具
+
系统运行时
+
Web API 平台

而且这个趋势还没有结束。

对于现在还在使用 Node.js 16/18 的项目来说,真正值得做的事情并不是为了追版本而升级,而是逐渐理解现代 Node.js 已经提供了哪些能力。

比如以前:

import axios from 'axios';

现在很多场景可以直接:

const response = await fetch(url);

以前:

nodemon app.js

现在:

node --watch app.js

以前:

npm run build

现在也可以:

node --run build

以前:

new Date()

处理复杂日期和时区时经常痛苦不堪。

未来则可以逐渐使用:

Temporal

这可能才是 Node.js 18 → 26 最值得关注的变化:

Node.js 正在从一个“运行 JavaScript 的环境”,逐渐变成一个越来越完整的现代应用运行平台。

而对于今天的新项目来说,与其问:

“Node.js 26 又增加了什么 API?”

不如开始思考:

“以前我安装的那些 npm 包,现在哪些已经可以被 Node.js 原生能力替代?”

这才是这几年 Node.js 演进真正有意思的地方。

#nodejs #后端
ZZC

ZZC

写代码,也写一些生活。