楼层: 首页/ 软件技术/ Node.js 全栈实战/ 内置核心模块:Node 白送的工具箱
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()。