05
内置核心模块:Node 白送的工具箱
Built-in Core Modules
写 Node 不用装任何包,它自己就带了一大堆内置模块:读写文件、拼路径、起 HTTP 服务、加密、处理二进制、调子进程……下面挑你天天用的几个讲,require 就能用,不花一分钱。
fs 文件系统:读写你的硬盘
fs 有三种风格的 API:同步版(直接阻塞,脚本用)、回调版(老写法)、Promise 版(fs/promises,配 async/await,现在的主力)。
fs/promises 版——读写文件的现代姿势
// 用 ESM 写法引入(package.json 里要写 "type":"module")
import fs from "node:fs/promises";
import path from "node:path";
async function demo() {
// 1. 写一个文件
await fs.writeFile("note.txt", "今天学 Node,真香。");
// 2. 读出来看看
const text = await fs.readFile("note.txt", "utf8");
console.log(text); // 今天学 Node,真香。
// 3. 往文件末尾追加一行
await fs.appendFile("note.txt", "\n追加的第二行。");
// 4. 列出某个文件夹里有什么
const files = await fs.readdir("./src");
console.log("src 下有:", files);
}
demo();
致命坏习惯:在 Web 服务器里用同步读文件
fs.readFileSync 是同步的——它会卡住整个 Node 进程直到文件读完。你在 Express 接口里用它读一个大文件,那一瞬间所有其他用户的请求全都排队等着,并发直接归零。Web 服务里一律用异步的 fs/promises 版本,同步 API 只写在命令行脚本里。
path 路径拼接:别用字符串硬拼
Windows 用反斜杠 \,Mac/Linux 用正斜杠 /。手拼路径换个系统就崩。用 path.join 自动适配:
import path from "node:path";
// join:把多段拼起来,自动处理斜杠,会把 . 和 .. 化简
path.join("src", "views", "home.html"); // "src/views/home.html"
// resolve:从当前目录一路算到一个绝对路径
path.resolve("src", "app.js"); // "/Users/你/项目/src/app.js"
http 模块:不装框架也能写 Web 服务器
下面这个例子只用 Node 自带的 http,不装任何框架,就能起一个能响应浏览器请求的服务器。理解它,你就理解了 Express 到底帮你省了什么。
用原生 http 模块写一个服务器
import http from "node:http";
// createServer 给你一个函数:每来一个请求就调一次
const server = http.createServer((req, res) => {
// req 是请求(来路),res 是响应(你要回的话)
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
if (req.url === "/") {
res.end(JSON.stringify({ msg: "你好,我是原生 Node 服务器" }));
} else if (req.url === "/about") {
res.end("关于页面");
} else {
res.writeHead(404);
res.end("页面不存在");
}
});
// 监听 3000 端口,浏览器访问 http://localhost:3000
server.listen(3000, () => console.log("跑在 http://localhost:3000"));
events 事件发射器:Node 的"广播台"
Node 到处是事件:流读完了、服务器连上了、按键按下了。EventEmitter 就是那个广播台——on 订阅,emit 广播。
import { EventEmitter } from "node:events";
const bus = new EventEmitter();
// 订阅:有人喊 "order" 事件时,执行这个函数
bus.on("order", (food) => console.log("收到一单:", food));
// 只听一次:once,触发一次后自动取消订阅
bus.once("start", () => console.log("程序启动了(只说一遍)"));
// 广播:emit 一个事件,所有 on 过的函数都会被调到
bus.emit("order", "红烧肉盖饭");
程序启动了(只说一遍)
收到一单: 红烧肉盖饭
stream 流:别让大文件把内存撑爆
你要把一个 2GB 的视频文件复制到另一个地方,如果一次性读进内存,Node 直接被你搞死。流的思路是"一点一点喂":读一口、写一口,像水管接水。pipe 就是把两根管子接起来。
// 把 big.mp4 边读边写到 copy.mp4,全程内存占用只有一小块
import fs from "node:fs";
const readStream = fs.createReadStream("big.mp4"); // 读水管
const writeStream = fs.createWriteStream("copy.mp4"); // 写水管
readStream.pipe(writeStream); // 把两根管子接上
流分四种:可读流(如读文件)、可写流(如写文件)、双工流(可读可写,如网络连接)、转换流(边读边加工,如压缩、转码)。
其他高频内置模块速查
| 模块 | 干嘛的,一句话 |
crypto | 加密:算哈希(md5/sha256)、HMAC、对称/非对称加密、生成安全随机数。存密码绝不自己手写加密,用它或 bcrypt。 |
buffer | 处理二进制数据:Buffer.from(字符串) 转字节,.toString() 转回来。网络、文件底层全靠它。 |
child_process | 在 Node 里执行系统命令:exec(跑完拿结果)、spawn(流式输出)、fork(拉起一个子 Node 进程)。 |
os | 读操作系统信息:CPU 核数、内存、主目录、换行符。配 PM2 cluster 时用得上。 |
url / querystring | 解析网址:把 ?name=abc&age=18 拆成对象。现在浏览器的 URL 构造器也能在 Node 用了。 |
util | 工具集:最常用 util.promisify,把老的回调风格函数转成 Promise 风格,就能 await 了。 |
记
本章小结
① 日常三件套:fs/promises 读写文件、path 拼路径、http 起服务。
② 服务器里别用同步 IO;大文件用 stream + pipe,别一把梭。
③ 事件/流是 Node 的母语,理解 EventEmitter 广播、流"一点点喂",后面学框架才不懵。
stream 进阶:用 pipeline 别用裸 pipe
前面讲流"一点点喂"。连接两个流用 src.pipe(dest) 很简单,但它有个大毛病:源头出错或中途断掉,下游不会自动报错也不会自动清理,容易内存泄漏。Node 官方推荐用 stream.pipeline(),它把错误传递、关闭清理、完成回调全包了。
pipeline:把读、解压、写串起来,任一环节出错都能感知并清理
const { createReadStream, createWriteStream } = require('node:fs');
const { createGzip } = require('node:zlib');
const { pipeline } = require('node:stream');
const { promisify } = require('node:util');
const pipelineAsync = promisify(pipeline); // 包成 await 版本
async function gzipFile(src, dest) {
await pipelineAsync(
createReadStream(src), // 读大文件
createGzip(), // 边读边 gzip 压缩
createWriteStream(dest) // 边压缩边写盘
);
console.log('压缩完成');
}
和 .pipe() 的区别:pipe 像"接上管子就走人",中间漏了没人管;pipeline 像"有人盯着整条管子,任何一段炸了立刻关阀门并报错"。生产环境处理文件/上传下载,一律用 pipeline。
crypto:哈希、HMAC、对称加密与签名
存密码、做接口签名、校验文件完整性,都要用 node:crypto,千万别自己造加密轮子。四个最常用场景:
crypto 四件套:哈希、HMAC、对称加密、随机数
const crypto = require('node:crypto');
// ① 哈希(不可逆):存密码要加盐慢哈希,别用 md5/sha1
const hash = crypto.createHash('sha256').update('hello').digest('hex');
// 密码推荐:crypto.scrypt 或 pbkdf2,慢且带盐
// ② HMAC(带密钥的哈希):接口请求签名,防篡改
const hmac = crypto.createHmac('sha256', '我的密钥').update('请求内容').digest('hex');
// ③ 对称加密 AES:能解密回去(存敏感字段)
const key = crypto.randomBytes(32); // 32 字节密钥,随机生成后妥善保管
const iv = crypto.randomBytes(16); // 每个加密用一个新 IV
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const enc = cipher.update('机密数据', 'utf8', 'hex') + cipher.final('hex');
// ④ 安全随机数(Math.random() 不安全,别拿来做 token)
const token = crypto.randomBytes(16).toString('hex');
防坑:① 存密码用 scrypt / argon2 / bcrypt 这种慢哈希 + 盐,别用 MD5/SHA256 直接存(彩虹表秒破);② 加密用 aes-256-gcm(自带完整性校验);③ 生成 token / 密钥用 crypto.randomBytes,永远别用 Math.random()。