Node系列 · Node基础:基础内置模块
Node 内置的核心模块里,
os/path/url/util是"几乎每个项目都会用"的四件套。它们不依赖任何第三方包,跨平台行为一致;理解它们的真实行为能避免一类经典的"在我电脑能跑"问题。
一、os:与操作系统交互
os模块提供操作系统层面的查询能力——CPU、内存、用户目录、网络接口等。常用 API:
| API | 返回类型 | 用途 |
|---|---|---|
os.EOL | string | 当前系统的行结束符(\n/\r\n) |
os.arch() | string | CPU 架构('arm64'/'x64') |
os.cpus() | array | 每颗逻辑 CPU 核心信息 |
os.freemem() | number | 空闲内存字节数 |
os.totalmem() | number | 总内存字节数 |
os.homedir() | string | 当前用户目录 |
os.hostname() | string | 主机名 |
os.tmpdir() | string | 系统临时目录 |
os.platform() | string | 平台标识('darwin'/'linux'/'win32') |
os.uptime() | number | 系统启动到现在的秒数 |
os.networkInterfaces() | object | 网络接口列表 |
典型应用:
// 根据 CPU 核心数决定要不要起 worker const cpuCount = os.cpus().length; const workers = Math.max(1, cpuCount - 1); // 把日志写到系统临时目录 const logDir = path.join(os.tmpdir(), 'my-app-logs'); fs.mkdirSync(logDir, { recursive: true });::: warning
不要拿os.cpus().length当真实物理核心数。它返回的是逻辑核心数(含超线程)。Node 单进程只能用一个 CPU 核心,物理多核需要 Worker Threads 或多进程。
:::
二、path:跨平台路径处理
path模块的核心价值是屏蔽 Windows / POSIX 路径差异。Node 代码里永远不要直接拼字符串路径,全部走pathAPI。
2.1 核心 API
| API | 作用 | 示例 |
|---|---|---|
path.basename(p) | 取文件名 | path.basename('/a/b/c.txt')→'c.txt' |
path.dirname(p) | 取目录 | path.dirname('/a/b/c.txt')→'/a/b' |
path.extname(p) | 取后缀 | path.extname('/a/b/c.txt')→'.txt' |
path.join(...p) | 拼接多段路径 | path.join('/a', 'b', 'c.txt')→'/a/b/c.txt' |
path.resolve(...p) | 解析为绝对路径 | path.resolve('c.txt')→<cwd>/c.txt |
path.relative(from, to) | 计算相对路径 | path.relative('/a/b', '/a/c/d')→'../c/d' |
path.isAbsolute(p) | 是否绝对路径 | path.isAbsolute('/a')→true |
path.normalize(p) | 规范化路径 | path.normalize('/a//b/./c')→'/a/b/c' |
path.sep | 当前系统的路径分隔符 | Linux\\(实际 /) / Windows\ |
path.delimiter | 环境变量分隔符 | Linux:/ Windows; |
2.2joinvsresolve
两个看着像,行为差异很大:
// join:纯拼接,结果是不是绝对路径看输入 path.join('/a', 'b', 'c.txt'); // '/a/b/c.txt' path.join('a', 'b', 'c.txt'); // 'a/b/c.txt' // resolve:从右往左拼,遇到绝对路径就重置起点 path.resolve('a', 'b', 'c.txt'); // <cwd>/a/b/c.txt path.resolve('/a', 'b', '/c.txt'); // '/c.txt'(遇到 /c.txt 重置)2.3 跨平台注意事项
// ❌ 错误:直接拼字符串 const filePath = __dirname + '/config/' + filename; // ✅ 正确:用 path.join const filePath = path.join(__dirname, 'config', filename); // ❌ 错误:硬编码分隔符 const tmpPath = '/tmp/' + name; // ✅ 正确:用 os.tmpdir() + path.join const tmpPath = path.join(os.tmpdir(), name);三、url:URL 解析与构造
url模块提供 WHATWG URL 标准实现(Node 10+)。
3.1 核心 API
| API | 用途 |
|---|---|
new URL(input, base?) | 构造一个 URL 对象 |
URLSearchParams | URL 查询字符串解析 |
url.fileURLToPath(url) | file://URL → 路径 |
url.pathToFileURL(path) | 路径 →file://URL |
3.2 URL 对象
const u = new URL('https://user:pass@example.com:8080/path/to?x=1&y=2#hash'); u.protocol; // 'https:' u.host; // 'example.com:8080' u.hostname; // 'example.com' u.port; // '8080' u.pathname; // '/path/to' u.search; // '?x=1&y=2' u.hash; // '#hash' u.username; // 'user' u.password; // 'pass' u.origin; // 'https://example.com:8080'3.3 查询参数
const u = new URL('https://example.com/api?x=1&y=2'); // 读取 u.searchParams.get('x'); // '1' u.searchParams.getAll('x'); // ['1'] u.searchParams.has('z'); // false // 增删改 u.searchParams.append('z', '3'); u.searchParams.set('x', '10'); u.searchParams.delete('y'); // 序列化 u.toString(); // 'https://example.com/api?x=10&z=3' // 单独构造 const params = new URLSearchParams({ foo: '1', bar: '2' }); params.toString(); // 'foo=1&bar=2'3.4 路径与 URL 互转
import { fileURLToPath, pathToFileURL } from 'node:url'; // path → file:// URL pathToFileURL('/usr/local/bin'); // 'file:///usr/local/bin' // file:// URL → path fileURLToPath('file:///usr/local/bin'); // '/usr/local/bin'ESM 模块下import.meta.url是file://URL,要拿路径必须fileURLToPath转一次(CJS 下直接是__dirname)。
四、util:工具函数集合
util模块聚集了各种"杂项但有用"的工具。
4.1 类型判断
util.isArray([]); // true util.isDate(new Date()); // true util.isRegExp(/x/); // true util.types.isPromise(Promise.resolve()); // true util.types.isMap(new Map()); // true util.types.isSet(new Set()); // true util.types.isArrayBuffer(new ArrayBuffer(8)); // true::: tip
Node 10+ 之后Array.isArray/instanceof已经够用,util.isArray等被视为遗留 API。新代码建议用util.types或原生Array.isArray。
:::
4.2 回调与 Promise 互转
// 旧的 CJS 回调风格 API:(err, value) => {...} // 想用 async/await?util.promisify 把它包成返回 Promise 的函数 const fs = require('fs'); const readFile = util.promisify(fs.readFile); const data = await readFile('./config.json', 'utf-8'); // 反向:Promise 风格 API 想给老代码用?util.callbackify const asyncAdd = async (a, b) => a + b; const cbAdd = util.callbackify(asyncAdd); cbAdd(1, 2, (err, sum) => { console.log(sum); // 3 });4.3 继承(inherits)
// ES6 class 时代几乎不用——直接用 extends 即可 // 留给老代码理解 function Animal() {} Animal.prototype.greet = function () { return 'hello'; }; function Dog() {} util.inherits(Dog, Animal); Dog.prototype.bark = function () { return 'woof'; }; const d = new Dog(); d.greet(); // 'hello' d.bark(); // 'woof'4.4 深度严格比较
util.isDeepStrictEqual( { a: 1, b: [1, 2] }, { a: 1, b: [1, 2] } ); // true // 与 '===' 的关键区别:递归比较对象、数组、Map、Set 1 === 1; // true { a: 1 } === { a: 1 }; // false util.isDeepStrictEqual( { a: 1 }, { a: 1 } ); // true4.5 调试输出
const obj = { a: 1, b: { c: [1, 2, 3] } }; console.log(util.inspect(obj, { depth: 4, colors: true }));util.inspect是console.log内部实现,可以指定深度、颜色、隐藏字段等。调试复杂对象时比直接JSON.stringify信息更全(保留函数、undefined、循环引用)。
五、其他常用内置模块速览
| 模块 | 用途 | 备注 |
|---|---|---|
querystring | URL 查询字符串 | URLSearchParams已覆盖大部分场景 |
assert | 断言(单元测试) | Node 自带测试时用,jest 流行后少用 |
events | EventEmitter | 见第 12 章 |
stream | 流处理 | 见第 7 章 |
crypto | 加密 / hash | 见后续章节 |
zlib | 压缩 / 解压 | gzip / deflate |
child_process | 子进程 | spawn / exec / fork |
六、最佳实践
| 场景 | 推荐做法 | 反例 |
|---|---|---|
| 拼接文件路径 | path.join/path.resolve | 字符串+拼 |
| 读用户目录 | os.homedir() | 假设是/home/x |
| 写临时文件 | os.tmpdir()+path.join | 假设是/tmp |
| 解析 URL | new URL(...)+searchParams | 手写 split |
| 老 API 转 Promise | util.promisify | 自己手写 wrapper |
| 判断内置类型 | util.types | instanceof跨 realm 不可靠 |
| 多行字符串拼接 | path.join或os.EOL | 硬编码\n/\r\n |
七、小结
os提供系统信息查询;os.cpus().length是逻辑核心数,含超线程path是跨平台路径处理的唯一正确选择;join拼接、resolve解析为绝对路径url用 WHATWG 标准;new URL+searchParams处理查询参数util.promisify把回调 API 包成 Promise;util.callbackify反向- 这些模块零依赖——新项目能少装一个包就少装一个