Node.js「标准库」= node: 内置模块。每个模块给定位、高频 API、示例与坑。这是 Node 工程的地基。
1. node:fs / node:fs/promises – 文件系统
描述
文件系统 API 三套:同步(readFileSync,阻塞)、回调(readFile,易嵌套)、Promise(fs/promises,现代首选)。大文件必须用流。
语法和示例
import { readFile, writeFile, appendFile, mkdir, rm, stat, readdir }
from 'node:fs/promises';
const txt = await readFile('a.txt', 'utf8'); // 必须传编码
await writeFile('b.txt', data);
await appendFile('b.txt', more);
await mkdir('x/y', { recursive: true });
await rm('dir', { recursive: true, force: true });
const info = await stat('a'); // size/mtime/isFile()
const files = await readdir('.');
// 流式读大文件
import { createReadStream } from 'node:fs';
for await (const chunk of createReadStream('big.log')) {}注意事项
- 不传编码时 readFile 返回 Buffer(不是字符串),后续字符串操作会出错
- 同步 API 会阻塞事件循环——只用于启动阶段或 CLI 脚本
- rm 带 recursive 时务必确认路径,误删不可恢复
- 文件句柄泄漏:createReadStream 用完要 close(或 pipeline 自动管)
- watch 在容器/NFS 上可能不生效(inotify 限制)
2. node:path – 路径处理
描述
跨平台路径拼接与解析。永远不要手写 ‘/’ 拼接(Windows 不兼容)。
语法和示例
import path from 'node:path';
path.join('a', 'b', 'c.txt'); // a/b/c.txt
path.resolve('a'); // 基于 cwd 的绝对路径
path.relative(from, to);
path.extname('x.tar.gz'); // '.gz'
path.basename('/a/b/c.txt', '.txt'); // 'c'
path.dirname('/a/b/c.txt'); // '/a/b'
path.parse('/a/b/c.txt'); // {dir,base,ext,name}
path.isAbsolute(p);
path.sep; // '/' 或 '\\'
path.delimiter; // ':' 或 ';'
// URL 与路径互转
import { pathToFileURL, fileURLToPath } from 'node:url';注意事项
- path.join 不做绝对化,path.resolve 会基于 cwd——两者语义不同
- extname(‘.bashrc’) 返回 ”(点开头不算扩展名)
- Windows 下路径含反斜杠,写正则要转义或先 normalize
- 跨平台比较路径前先 path.normalize(大小写规则也不同)
- file:// URL 里的空格会被编码为 %20——用 fileURLToPath 解
3. node:http – HTTP 服务与请求
描述
底层 HTTP 服务(IncomingMessage/ServerResponse 流式)。现代客户端请求用全局 fetch(18+)。
语法和示例
import http from 'node:http';
const server = http.createServer((req, res) => {
// req.method / req.url / req.headers
if (req.url === '/health') {
res.writeHead(200, { 'content-type': 'application/json' });
return res.end(JSON.stringify({ ok: true }));
}
res.writeHead(404).end();
});
server.listen(3000);
// 读取请求体(流式)
const chunks = [];
req.on('data', c => chunks.push(c));
req.on('end', () => JSON.parse(Buffer.concat(chunks)));
// 优雅关闭
process.on('SIGTERM', () => server.close(() => process.exit(0)));注意事项
- res.end 必须调用,否则连接挂起(客户端一直等)
- 请求体是流,不读完不能解析;大请求体要限流防内存爆
- 默认没有路由/中间件——要这些用 Hono/Fastify/Express
- writeHead 之后不能再 setHeader(会报错)
- 生产要设各类超时(server.requestTimeout / headersTimeout),否则慢连接攻击
4. node:events – EventEmitter
描述
事件发射器:on/once/emit/off/prependListener。Node 大量内置对象(stream/net/server)都继承它。
语法和示例
import { EventEmitter } from 'node:events';
const bus = new EventEmitter();
bus.on('tick', (n) => console.log(n));
bus.once('ready', () => {});
const handler = () => {};
bus.on('data', handler);
bus.off('data', handler); // 需同一引用
bus.emit('tick', 1);
bus.listenerCount('tick');
bus.setMaxListeners(20); // 默认 10 时会打警告
// 一次性聚合
import { once } from 'node:events';
await once(bus, 'ready');
// 错误事件必须监听
bus.on('error', (e) => console.error(e));注意事项
- 没有 ‘error’ 监听时 emit(‘error’) 会直接抛异常崩进程
- off 需要同一函数引用,绑定过的箭头函数拿不回引用——用具名函数
- 监听器超过 10 个会有警告(内存泄漏提示),不是错误
- removeAllListeners 在库代码里用很危险(会清掉别人的监听)
- 监听器同步执行,抛错会中断 emit——异步逻辑要自己包 try
5. node:stream – 流与背压
描述
五种流:Readable/Writable/Duplex/Transform/PassThrough。pipeline 自动处理背压与错误清理,是正确做法。
语法和示例
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline, Transform } from 'node:stream';
import zlib from 'node:zlib';
// 标准管道(自动背压 + 出错清理全部流)
await pipeline(
createReadStream('in.log'),
zlib.createGzip(),
createWriteStream('out.gz'),
);
// 自定义 Transform
const upper = new Transform({
transform(chunk, enc, cb) {
cb(null, chunk.toString().toUpperCase());
},
});
// 从可迭代建流 / 异步迭代消费
import { Readable } from 'node:stream';
Readable.from(['a', 'b']);
for await (const chunk of createReadStream('f')) {}
// 对象模式
new Transform({ objectMode: true, transform(o, e, cb) { cb(null, o); } });注意事项
- 不要手写 .pipe() 链——它不传播错误且不清理中间流,用 pipeline
- 流默认缓冲 16KB(highWaterMark),大对象场景要调
- 消费不掉数据会导致背压堆积内存——必须及时消费或暂停
- objectMode 的 chunk 是任意对象(不限 Buffer)
- 流出错后不会自动销毁所有环节(除 pipeline),可能句柄泄漏
6. node:crypto – 加密与随机
描述
哈希、HMAC、随机、密码派生、常量时间比较、WebCrypto。密码存储用 scrypt/bcrypt,不要用 sha256 直接存。
语法和示例
import crypto from 'node:crypto';
crypto.randomUUID(); // v4 UUID
crypto.randomBytes(16).toString('hex');
crypto.randomInt(1, 101);
crypto.createHash('sha256').update(s).digest('hex');
crypto.createHmac('sha256', key).update(s).digest('hex');
// 密码派生(scrypt 推荐)
crypto.scrypt(pw, salt, 64, (err, key) => {});
// 常量时间比较(防时序攻击)
crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
// AES-GCM
const c = crypto.createCipheriv('aes-256-gcm', key, iv);
c.setAuthTag; c.update; c.final();
// WebCrypto(异步,符合标准)
crypto.subtle.digest('SHA-256', data);注意事项
- 密码不要用 sha256 直接哈希——用 scrypt/bcrypt/argon2(加盐 + 慢)
- timingSafeEqual 要求两个 Buffer 等长,否则直接抛错
- AES-GCM 的 IV 每次必须唯一,复用会完全破坏安全
- randomUUID 是 v4(随机),不能排序当主键
- Node 18+ 的 crypto 全局可用(webcrypto),但仍需 import 使用大部分 API
7. node:child_process – 子进程
描述
exec(走 shell,拼接有注入风险)、execFile(无 shell,参数数组,安全)、spawn(流式,大输出)、fork(Node 子进程带 IPC)。
语法和示例
import { execFile, spawn, fork } from 'node:child_process';
import { promisify } from 'node:util';
// execFile:安全(参数数组,不过 shell)
const run = promisify(execFile);
const { stdout, stderr } = await run('git', ['status'], { timeout: 30_000 });
// spawn:流式处理大输出
const child = spawn('ping', ['-c', '4', 'example.com']);
child.stdout.on('data', d => process.stdout.write(d));
child.on('close', code => console.log('exit', code));
// fork:Node 子进程 + IPC
const w = fork('./worker.js');
w.send({ job: 1 });
w.on('message', m => {});
// 选项
{ cwd: '/tmp', env: { ...process.env } }注意事项
- exec 会把命令交给 shell,用户输入拼进去就是命令注入——用 execFile
- execFile 有默认 maxBuffer(1MB),大输出会报错,调整或改 spawn
- 子进程不自动随父退出,需显式 kill 或监听父进程退出
- spawn 的 stdio 默认 ‘pipe’,不消费会因缓冲区满而阻塞子进程
- fork 才有的 .send/.on(‘message’);spawn 需自己走 stdio
8. node:worker_threads – 工作线程
描述
CPU 密集任务绕开单线程限制。通过 workerData 传入、parentPort 通信;共享内存用 SharedArrayBuffer + Atomics。
语法和示例
import { Worker, isMainThread, parentPort, workerData }
from 'node:worker_threads';
if (isMainThread) {
const w = new Worker(new URL(import.meta.url), { workerData: { n: 1e8 } });
w.on('message', r => console.log('结果', r));
w.on('error', e => console.error(e));
w.on('exit', code => {});
} else {
parentPort.postMessage(heavy(workerData.n));
}
// 线程池(常见做法:固定 N 个 worker + 队列)注意事项
- IO 密集任务不要用 worker——异步 IO 已足够,worker 反而费内存
- worker 之间不共享状态,只能传消息或共享内存
- 传大数据用 transferList 转移所有权(避免结构化克隆拷贝)
- 每个 worker 有独立 JS 堆(约 10MB 起),上百个 worker 会 OOM
- worker 里抛未捕获异常会触发 ‘error’ 事件并终止线程
9. node:url / node:os / node:util / node:zlib
描述
四个工具模块打包速查:URL 解析、系统信息、promisify/inspect/parseArgs、压缩。
语法和示例
// url
const u = new URL('https://a.b/x?p=2#f');
u.pathname; u.searchParams.get('p'); u.searchParams.set('p', 3);
new URLSearchParams({ a: 1, b: '中文' }).toString();
// os
import os from 'node:os';
os.cpus().length; os.totalmem(); os.homedir(); os.tmpdir(); os.EOL;
// util
import util from 'node:util';
const sleep = util.promisify(setTimeout);
util.inspect(obj, { depth: 4, colors: true });
util.parseArgs({ options: { n: { type: 'string', short: 'n' } } });
util.types.isPromise(x);
// zlib
import zlib from 'node:zlib';
zlib.gzipSync(buf); zlib.gunzipSync(gz);
zlib.brotliCompressSync(buf); // 更高压缩比注意事项
- URL 会自动编码,但构造后改 searchParams 才会同步到 href
- os.cpus().length 返回逻辑核数,容器里可能看到宿主核数(用 cgroup 限制需另查)
- util.promisify 只对「最后参数是 callback」的函数有效
- parseArgs 是 18.3+ 才有,简单 CLI 够用,复杂场景用 commander
- brotli 压缩慢但比 gzip 小 15-20%,静态资源值得,动态响应要权衡 CPU
10. node:readline / node:timers/promises / node:test
描述
行读取(CLI 交互与大文件)、Promise 定时器、内置测试器。
语法和示例
// readline 问答
import readline from 'node:readline/promises';
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const name = await rl.question('名字? ');
rl.close();
// 处理大文件行
import { createInterface } from 'node:readline';
for await (const line of createInterface({ input: stream })) {}
// Promise 定时器
import { setTimeout as sleep } from 'node:timers/promises';
await sleep(1000);
for await (const t of setInterval(500)) { if (cond()) break; }
// 内置测试(18+)
import { test } from 'node:test';
import assert from 'node:assert/strict';
test('add', () => assert.equal(1 + 1, 2));
// 运行:node --test注意事项
- readline 的 question 必须 await,否则会读到 undefined
- createInterface 处理完要 close,否则 stdin 不释放进程不退出
- setInterval 异步迭代器的 break 会清理定时器(比手写 clearInterval 安全)
- node:test 的断言用 assert/strict 才是严格模式
- 测试文件命名 *.test.js 才会被 node –test 自动发现