跳到主要内容

Node.js 新特性

问题

Node.js 近年来有哪些重要的新特性?

答案

Node.js 持续演进,引入了原生 ESM 支持、内置 fetch、测试运行器、权限模型等重要特性。


ESM 原生支持

启用 ESM

// package.json
{
"type": "module"
}
// 或使用 .mjs 扩展名
// app.mjs

// ESM 导入
import express from 'express';
import { readFile } from 'fs/promises';
import config from './config.js'; // 必须带扩展名

// 动态导入
const module = await import('./dynamic.js');

ESM 与 CommonJS 互操作

// ESM 中导入 CommonJS
import pkg from 'cjs-package'; // 默认导出
import { createRequire } from 'module';

const require = createRequire(import.meta.url);
const cjsModule = require('./legacy.cjs');

// 获取 __dirname 和 __filename
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// import.meta
console.log(import.meta.url); // file:///path/to/file.js
console.log(import.meta.resolve); // 解析模块路径

内置 fetch(Node 18+)

// 无需安装 node-fetch
const response = await fetch('https://api.example.com/users');
const data = await response.json();

// POST 请求
const result = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Alice' })
});

// AbortController 支持
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);

try {
const response = await fetch(url, { signal: controller.signal });
const data = await response.json();
} catch (err) {
if (err.name === 'AbortError') {
console.log('Request timed out');
}
} finally {
clearTimeout(timeout);
}

内置测试运行器(Node 18+)

// test/user.test.ts
import { describe, it, before, after, mock } from 'node:test';
import assert from 'node:assert/strict';

describe('User Service', () => {
before(() => {
// 测试前准备
});

after(() => {
// 测试后清理
});

it('should create user', async () => {
const user = await createUser({ name: 'Alice' });
assert.equal(user.name, 'Alice');
assert.ok(user.id);
});

it('should throw on invalid input', async () => {
await assert.rejects(
() => createUser({ name: '' }),
{ message: 'Name is required' }
);
});
});

// Mock
import { mock } from 'node:test';

it('should mock function', () => {
const fn = mock.fn((x: number) => x * 2);

assert.equal(fn(2), 4);
assert.equal(fn.mock.calls.length, 1);
assert.deepEqual(fn.mock.calls[0].arguments, [2]);
});
# 运行测试
node --test
node --test --watch # 监听模式
node --test --coverage # 覆盖率
node --test --test-reporter spec # 报告格式

权限模型(Node 20+ 实验性)

# 启用权限模型
node --experimental-permission app.js

# 文件系统权限
node --experimental-permission \
--allow-fs-read=/app \
--allow-fs-write=/tmp \
app.js

# 网络权限
node --experimental-permission \
--allow-worker \
--allow-child-process \
app.js
// 检查权限
import { permission } from 'node:process';

if (permission.has('fs.read', '/path')) {
// 有读取权限
}

单文件可执行(Node 20+)

# 创建 SEA (Single Executable Application)

# 1. 创建配置
echo '{ "main": "app.js", "output": "sea-prep.blob" }' > sea-config.json

# 2. 生成 blob
node --experimental-sea-config sea-config.json

# 3. 复制 Node 可执行文件
cp $(which node) my-app

# 4. 注入 blob
npx postject my-app NODE_SEA_BLOB sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2

# 5. 运行
./my-app

watch 模式(Node 18+)

# 内置 watch 模式,无需 nodemon
node --watch app.js

# 指定监听路径
node --watch-path=./src --watch-path=./config app.js

# 忽略文件
node --watch --watch-preserve-output app.js

新增 API

Web Streams API

import { ReadableStream, WritableStream, TransformStream } from 'stream/web';

// Web 标准 Stream
const readable = new ReadableStream({
start(controller) {
controller.enqueue('Hello');
controller.enqueue('World');
controller.close();
}
});

const transform = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase());
}
});

const writable = new WritableStream({
write(chunk) {
console.log(chunk);
}
});

await readable.pipeThrough(transform).pipeTo(writable);

结构化克隆

import { structuredClone } from 'util';

const original = {
date: new Date(),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
regex: /pattern/g,
buffer: new ArrayBuffer(8)
};

const clone = structuredClone(original);
// 正确复制所有类型

自定义 ESM Loader(已更名为 Hooks)

// loader.mjs
export async function resolve(specifier, context, nextResolve) {
// 自定义模块解析
if (specifier.startsWith('@/')) {
specifier = specifier.replace('@/', './src/');
}
return nextResolve(specifier, context);
}

export async function load(url, context, nextLoad) {
// 自定义加载行为
return nextLoad(url, context);
}
node --loader ./loader.mjs app.js
# Node 20+
node --import ./register.mjs app.js

常见面试问题

Q1: Node.js ESM 和 CommonJS 有什么区别?

答案

特性ESMCommonJS
语法import/exportrequire/module.exports
加载异步同步
解析静态分析运行时
thisundefinedmodule.exports
扩展名必须可选
顶层 await支持不支持
Tree Shaking支持不支持
// ESM
import { readFile } from 'fs/promises';
export const helper = () => {};

// CommonJS
const { readFile } = require('fs').promises;
module.exports = { helper: () => {} };

Q2: Node.js 内置 fetch 和 node-fetch 有什么区别?

答案

特性内置 fetchnode-fetch
安装无需安装需要安装
APIWeb 标准接近标准
依赖内置外部依赖
Node 版本18+任意版本
底层Undicihttp 模块
// 内置 fetch(Node 18+)
const res = await fetch(url);

// 向后兼容
const fetch = globalThis.fetch || (await import('node-fetch')).default;

Q3: Node.js 内置测试运行器有什么优势?

答案

优势说明
零依赖无需安装 Jest/Mocha
原生支持官方维护
TypeScript配合 ts-node 使用
覆盖率内置覆盖率报告
隔离每个测试文件独立进程
# 基本使用
node --test

# 配合 TypeScript
node --import tsx --test test/*.test.ts

# 覆盖率
node --test --experimental-test-coverage

Q4: 如何在 ESM 中获取 __dirname?

答案

// CommonJS 中
console.log(__dirname);
console.log(__filename);

// ESM 中
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// 或者直接使用 import.meta
console.log(import.meta.url); // file:///path/to/file.js

// Node 20.11+ 新增
console.log(import.meta.dirname);
console.log(import.meta.filename);

Q5: Node.js 各主要版本有哪些重要特性?

答案

版本重要特性
Node 16V8 9.0、Apple Silicon、npm 7
Node 18内置 fetch、测试运行器、watch 模式
Node 20权限模型、WebSocket、稳定测试运行器
Node 22WebSocket 客户端、require(esm)
// Node 18: 内置 fetch
const data = await fetch(url);

// Node 18: 内置测试
import { test } from 'node:test';

// Node 20: import.meta 增强
console.log(import.meta.dirname);

// Node 22: CommonJS 可以 require ESM
const esm = require('./esm-module.mjs');

相关链接