速查表的价值在于“可查阅”。按功能域分组、标注同步/异步/流式差异、给出最小可用示例。以下内容按模块划分,每个模块先交代设计意图,再以表格加代码片段的方式呈现核心接口。
一、fs:文件系统操作的接口分层
Node.js的fs模块是API设计的一个典型样本。同一功能通常提供三种形态:回调版、fs/promises版、以及Sync同步版。同步版在启动脚本或CLI工具中方便,但在服务端请求路径上直接调用会阻塞事件循环,这是需要留意的边界。
高频接口速查
| 接口 | 说明 | 形态 |
|---|---|---|
fs.readFile |
读取整个文件 | 回调/Promise/Sync |
fs.writeFile |
写入文件,不存在则创建 | 同上 |
fs.appendFile |
追加内容 | 同上 |
fs.mkdir |
创建目录,recursive:true支持嵌套 |
同上 |
fs.readdir |
读取目录内容 | 同上 |
fs.stat |
获取文件状态(大小、类型、时间戳) | 同上 |
fs.rename |
重命名或移动 | 同上 |
fs.unlink |
删除文件 | 同上 |
fs.rm |
删除文件或目录,替代旧的rmdir |
同上 |
fs.createReadStream |
创建可读流 | 流式 |
fs.createWriteStream |
创建可写流 | 流式 |
fs.watch |
监听文件变化 | 事件式 |
fs/promises是现在化用法中的优选,尤其在async/await上下文中。一个容易忽略的差异是:fs/promises的readFile返回Buffer,如果不传编码会得到原始字节,而非字符串。在处理非UTF-8文件时需显式指定encoding。
示例:读取配置文件并捕获特定错误码。
const fs = require('fs/promises');
async function loadConfig(filePath) {
try {
const raw = await fs.readFile(filePath, 'utf8');
return JSON.parse(raw);
} catch (err) {
if (err.code === 'ENOENT') {
throw new Error(`配置文件不存在: ${filePath}`);
}
throw err;
}
}
二、path:跨平台路径处理的确定性工具集
path模块是Node.js中少有的“纯函数”模块——不产生I/O,只做字符串计算。它的核心价值在于抹平POSIX与Windows之间的分隔符差异。开发中经常看到手动拼接dir+'/'+file的写法,这在Windows上会埋下隐患。path.join和path.resolve是两个使用频率最高的接口,但它们的语义有明确区别:join只是拼接并规范化分隔符,resolve则会从右向左解析,直到构造出绝对路径。
| 接口 | 行为 |
|---|---|
path.join(...segments) |
用平台分隔符拼接,规范化结果 |
path.resolve(...segments) |
生成绝对路径,从右向左解析 |
path.basename(p,ext?) |
取文件名,可去除扩展名 |
path.dirname(p) |
取目录部分 |
path.extname(p) |
取扩展名(含.) |
path.normalize(p) |
规范化路径中的..与. |
path.relative(from,to) |
计算相对路径 |
path.sep |
平台分隔符,POSIX为/,Windows为\ |
path.resolve的行为需要特别留意:如果参数列表中某一项是绝对路径,前面所有参数会被丢弃。这也是它在处理用户输入时比join更安全的原因之一。
三、http与https:服务端与客户端接口
http模块的接口设计保留了Node.js早期“底层但灵活”的风格。createServer返回http.Server实例,其回调接收IncomingMessage(请求)与ServerResponse(响应)。请求体不会自动解析,需要通过data事件累积或使用流式处理。这是与Express等框架最直观的差异——框架帮你做了body解析,但代价是隐藏了流控细节。
| 接口 | 用途 |
|---|---|
http.createServer([options],handler) |
创建HTTP服务 |
http.request(options[,callback]) |
发起HTTP请求 |
http.get(url[,options][,callback]) |
request的GET快捷方式 |
server.listen(port[,host][,backlog][,callback]) |
监听端口 |
server.close([callback]) |
停止接收新连接 |
req.on('data',chunk=>{}) |
流式接收请求体 |
res.writeHead(status,headers) |
写入状态码与响应头 |
res.write(chunk) |
写入响应体分片 |
res.end([data]) |
结束响应 |
一个常见误区:在http.createServer中直接调用res.end()后仍然尝试res.write(),会触发ERR_STREAM_WRITE_AFTER_END。响应流在end()调用后即进入关闭流程,后续写入不再被接受。
四、stream:流式数据处理的四类抽象
Stream是Node.js中抽象层级较高的一组接口,也是理解成本较高的部分。四种基本类型——Readable、Writable、Duplex、Transform——对应了数据流动的不同方向与转换场景。pipeline是处理多级流转时的推荐方式,它会在任意环节出错时统一销毁所有流,避免资源泄漏。
| 接口 | 说明 |
|---|---|
stream.Readable |
可读流,数据源 |
stream.Writable |
可写流,数据目标 |
stream.Duplex |
双向流,如TCPsocket |
stream.Transform |
转换流,读写间做处理 |
stream.pipeline(...streams,callback) |
管道串联,自动错误清理 |
readable.pipe(dest) |
基础管道,不处理错误传播 |
stream.finished(stream,callback) |
监听流结束或出错 |
stream/promises.pipeline |
Promise版pipeline |
pipe与pipeline的选择:简单场景下pipe够用,但一旦上游出错,下游不会被自动关闭。pipeline解决了这个问题,它在生产环境中更稳妥。
五、crypto:哈希、加密与随机数
crypto模块提供了底层密码学原语。createHash用于单向摘要,createHmac用于带密钥的摘要,randomBytes用于生成密码学安全的随机值。createCipheriv和createDecipheriv需要显式指定算法、密钥和IV,不再使用已废弃的createCipher。
| 接口 | 用途 |
|---|---|
crypto.createHash(algo) |
创建哈希对象,update+digest |
crypto.createHmac(algo,key) |
创建HMAC |
crypto.randomBytes(size) |
生成安全随机字节 |
crypto.createCipheriv(algo,key,iv) |
对称加密 |
crypto.createDecipheriv(algo,key,iv) |
对称解密 |
crypto.timingSafeEqual(a,b) |
恒定时间比较,防时序攻击 |
示例:验证webhook签名的恒定时间比较。
const crypto = require('crypto');
function verifySignature(payload, secret, receivedSig) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const expectedBuf = Buffer.from(expected, 'hex');
const receivedBuf = Buffer.from(receivedSig, 'hex');
if (expectedBuf.length !== receivedBuf.length) return false;
return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}
timingSafeEqual的长度检查是必要的——长度不同时会抛出异常,而不是返回false。
六、child_process与worker_threads:并行执行的两种路径
child_process与worker_threads解决的是不同层面的并行需求。child_process启动的是独立进程,拥有独立的内存空间和V8实例,适合运行外部命令或隔离度要求高的任务。worker_threads则在同一个进程内创建线程,共享部分内存(通过SharedArrayBuffer),线程间通信开销更低,适合CPU密集型计算的拆分。
child_process核心接口
| 接口 | 行为 |
|---|---|
spawn(command[,args][,options]) |
启动进程,返回流式接口 |
exec(command[,options][,callback]) |
执行命令,缓冲输出后回调 |
execFile(file[,args][,options][,callback]) |
执行可执行文件,不经过shell |
fork(modulePath[,args][,options]) |
启动Node子进程,内置IPC通道 |
worker_threads核心接口
| 接口 | 行为 |
|---|---|
newWorker(filename[,options]) |
创建工作线程 |
worker.postMessage(value) |
向工作线程发送消息 |
parentPort.on('message',handler) |
在主线程接收消息 |
worker.terminate() |
强制终止线程 |
isMainThread |
判断当前是否为主线程 |
exec与execFile的差异在于shell解析:exec会将命令字符串交给shell,支持管道和通配符,但也带来注入风险。execFile直接执行可执行文件,不经过shell,在参数可控的场景下更安全。
七、events与EventEmitter:异步架构的通信基座
EventEmitter是Node.js中许多核心类的基类,包括http.Server、stream.Readable、net.Socket等。它的接口不多,但组合方式灵活。once注册的监听器在首次触发后自动移除,适合一次性初始化完成的通知。removeListener需要传入与原监听器相同的函数引用,匿名函数无法移除——这是实际调试中容易踩的坑。
| 接口 | 说明 |
|---|---|
emitter.on(event,listener) |
注册监听器 |
emitter.once(event,listener) |
注册一次性监听器 |
emitter.emit(event,...args) |
触发事件 |
emitter.off(event,listener) |
移除监听器(removeListener别名) |
emitter.listenerCount(event) |
获取监听器数量 |
emitter.setMaxListeners(n) |
调整监听器数量上限 |
默认最大监听器数为10。超过时会打印警告,但不会阻止注册。在内存紧张的长驻进程中,大量监听器未及时移除是常见泄漏源。
八、process与全局能力:进程级接口
process是全局对象,无需require。它的接口横跨环境变量、进程控制、性能计时、信号处理等多个维度。process.nextTick与setImmediate的调度层级不同:nextTick在当前操作完成后、事件循环进入下一阶段前执行,setImmediate则在check阶段执行。nextTick的递归调用会耗尽事件循环,需要留意。
| 接口 | 说明 |
|---|---|
process.argv |
命令行参数数组 |
process.env |
环境变量对象 |
process.exit([code]) |
同步退出进程 |
process.nextTick(cb) |
当前操作后立即执行 |
process.hrtime.bigint() |
高精度时间,返回BigInt |
process.memoryUsage() |
内存使用量 |
process.on('uncaughtException',fn) |
未捕获异常兜底 |
process.on('unhandledRejection',fn) |
未处理Promise拒绝兜底 |
uncaughtException监听器不是错误恢复手段。在异常发生后,进程状态可能已不可靠,正确的做法是在监听器中记录信息后退出,由外部工具重启。
九、本节课程知识要点
速查表的使用方式是“查阅”而非“通读”。建议在编辑器或笔记工具中保留一份按模块索引的版本,遇到接口记忆模糊时快速定位。更重要的是理解各接口的边界:同步接口的阻塞代价、流式接口的错误传播机制、并行方案中进程与线程的取舍。这些判断比记住函数签名更能影响代码质量。
练习思考题
-
fs.promises.readFile与fs.createReadStream在读取大文件时的内存行为有何差异?什么场景下应当优先选择流式读取? -
path.join和path.resolve在传入/foo与bar时分别返回什么?为什么resolve在处理用户上传路径时可能更合适? -
pipeline相比pipe在错误处理上做了哪些额外工作?如果不用pipeline,需要手动处理哪些事件? -
worker_threads的postMessage传递对象时是结构化克隆还是共享内存?SharedArrayBuffer在什么条件下才能在线程间共享?