基础语法
本文档详细介绍 Node.js 的核心语法特性,包括全局对象、模块系统、文件操作等基础知识。
ECMAScript 标准演进与 Node.js 支持
ECMAScript 标准简史
JavaScript 最初由网景浏览器于 1995 年发布,名为 LiveScript,后更名为 JavaScript。随后提交至 ECMA(欧洲计算机制造商协会)进行标准化,官方学名为 ECMAScript。由于 ECMAScript 名称不够通俗,开发者仍习惯使用 JavaScript。
ES 标准的演进并非一帆风顺。标准协会负责制定规范,而实际落地执行的是浏览器厂商,各方对标准的认同度不一,导致标准推进缓慢。1999 年 ES3 至 2009 年 ES5 的十年间,语言标准几乎停滞不前。期间曾推出 ES4,但因改动过于激进而废弃,未在厂商侧落地。直到 2009 年,语言标准才重新步入正轨。
版本命名说明:
| 术语 | 说明 |
|---|---|
| JS | JavaScript 的简称,代指实现了 ECMAScript 标准的任何语言版本 |
| ES | ECMAScript 的简称 |
| ES2/3/4/5 | 语言标准的版本号 |
| ES2015/ES2016 | 以发布年份命名的标准,如 ES2015 与 ES6 等价 |
| ES.Next | 代指下一个即将推出的语言标准,为动态概念 |
Node.js 对 ES 标准的支持演进
Node.js 对 ECMAScript 标准的支持程度随版本迭代逐步完善:
| Node.js 版本 | ES 标准支持情况 |
|---|---|
| v6.14.4+ | 支持 99% 以上的 ES6 语法特性 |
| v10.x+ | 支持几乎全部的 ES7/ES2016、ES8/ES2017、ES9/ES2018 可用语法特性 |
| v12.x+ | globalThis 稳定支持,ES Modules 实验性支持 |
| v14.x+ | ES Modules 稳定支持,可选链、空值合并运算符 |
| v16.x+ | 顶层 await 支持 |
| v18.x+ | fetch 实验性支持 |
| v22.x+ | fetch、WebSocket、Web Streams 等稳定支持 |
可通过 node.green 查询各 Node.js 版本对 ES 语法特性的详细支持情况。
文档目录:
- ECMAScript 标准演进与 Node.js 支持:ES 标准简史、Node.js 对各版本的支持情况
- 全局对象和全局变量:console、process、Buffer、global 等
- 定时器函数:setTimeout、setInterval、setImmediate、process.nextTick
- 路径变量:__dirname 和 __filename
- 模块系统:require、module.exports、模块加载机制
- 实战视角:Webpack 源码中的 ES6+ 语法特性:真实工程中的语法特性应用实例
- 文件系统基础:fs 模块的同步/异步/Promise API
- 案例实战:命令行动画龟兔赛跑:ES6+ 语法特性的综合实战演示
- 常见问题解答:开发过程中的常见问题
全局对象和全局变量
Node.js 提供一些全局可用的对象和变量,无需引入即可直接使用。
全局对象概览
┌─────────────────────────────────────────────────────────────┐
│ Node.js 全局对象 │
├─────────────────────────────────────────────────────────────┤
│ 调试输出 │ console │
│ 进程控制 │ process │
│ 二进制数据 │ Buffer │
│ 全局命名 │ global、globalThis │
│ 定时器 │ setTimeout、setInterval、setImmediate │
│ 路径信息 │ __dirname、__filename │
│ 模块相关 │ require、module、exports │
│ URL 处理 │ URL、URLSearchParams │
│ 文本编码 │ TextEncoder、TextDecoder │
└─────────────────────────────────────────────────────────────┘console 对象
console 对象提供了控制台输出功能,是调试和日志记录的重要工具。
常用方法
| 方法 | 说明 | 输出流 |
|---|---|---|
log() | 普通输出 | stdout |
error() | 错误输出 | stderr |
warn() | 警告输出 | stderr |
info() | 信息输出(等同于 log) | stdout |
debug() | 调试输出(仅在调试模式下显示) | stdout |
table() | 表格形式输出 | stdout |
dir() | 以对象形式输出(可控制深度) | stdout |
time() | 开始计时 | - |
timeEnd() | 结束计时并输出结果 | stdout |
timeLog() | 输出计时过程中的时间点 | stdout |
trace() | 输出当前调用堆栈 | stderr |
assert() | 断言(条件为 false 时输出错误) | - |
clear() | 清空控制台 | - |
count() | 计数器 | stdout |
countReset() | 重置计数器 | - |
group() | 开始分组 | stdout |
groupEnd() | 结束分组 | - |
基本输出
// 普通输出
console.log("Hello Node.js")
// 错误输出(输出到 stderr)
console.error("这是一个错误信息")
// 警告输出
console.warn("这是一个警告信息")
// 信息输出
console.info("这是一条信息")
// 调试输出(仅在调试模式下显示)
console.debug("调试信息")
// 清空控制台
console.clear()对象输出
// 输出对象(格式化显示)
const obj = { name: "Node.js", version: "20.0.0" }
console.log(obj)
// 使用 console.dir 控制输出深度
const deepObj = {
level1: {
level2: {
level3: {
level4: "deep value"
}
}
}
}
console.dir(deepObj, { depth: null }) // 显示所有层级
console.dir(deepObj, { depth: 2 }) // 只显示到 level3表格输出
// 表格形式输出数组
console.table([
{ name: "Alice", age: 25, city: "北京" },
{ name: "Bob", age: 30, city: "上海" },
{ name: "Charlie", age: 35, city: "广州" }
])
// 输出对象
console.table({
node: { version: "20.0.0", lts: true },
npm: { version: "10.0.0", lts: false }
})
// 只显示特定列
console.table(
[
{ name: "Alice", age: 25, city: "北京" },
{ name: "Bob", age: 30, city: "上海" }
],
["name", "age"] // 只显示 name 和 age 列
)计时功能
// 基本计时
console.time("timer")
// 执行一些操作
for (let i = 0; i < 1000000; i++) {}
console.timeEnd("timer") // 输出: timer: 5.123ms
// 计时过程中输出
console.time("operation")
for (let i = 0; i < 3; i++) {
// 执行操作
console.timeLog("operation", `第 ${i + 1} 次迭代完成`)
}
console.timeEnd("operation")计数器
// 计数器示例
function processItem(item) {
console.count("处理项目")
console.count(`项目: ${item}`)
}
processItem("A") // 处理项目: 1, 项目: A: 1
processItem("B") // 处理项目: 2, 项目: B: 1
processItem("A") // 处理项目: 3, 项目: A: 2
// 重置计数器
console.countReset("处理项目")分组输出
// 分组输出
console.group("用户信息")
console.log("姓名: 张三")
console.log("年龄: 25")
console.group("联系方式")
console.log("电话: 13800138000")
console.log("邮箱: zhangsan@example.com")
console.groupEnd()
console.groupEnd()
// 折叠分组
console.groupCollapsed("详细信息")
console.log("这是一些详细信息")
console.groupEnd()堆栈跟踪
// 堆栈跟踪
function funcA() {
funcB()
}
function funcB() {
funcC()
}
function funcC() {
console.trace("调用堆栈信息")
}
funcA()断言
// 断言(条件为 false 时输出错误)
console.assert(1 === 2, "1 不等于 2") // 会输出错误信息
console.assert(true, "这不会输出") // 不会输出
// 对象断言
const user = { name: "张三", age: 25 }
console.assert(user.age >= 18, "用户未成年")格式化输出
// 使用占位符
console.log("姓名: %s, 年龄: %d", "张三", 25)
// 输出: 姓名: 张三, 年龄: 25
// 占位符类型
// %s - 字符串
// %d - 数字
// %i - 整数
// %f - 浮点数
// %j - JSON
// %o - 对象
// %O - 对象(详细)
const obj = { name: "Node.js", version: "20.0.0" }
console.log("对象: %j", obj)
console.log("对象详情: %O", obj)推荐使用模板字符串:
// 使用模板字符串(推荐)
const name = "张三"
const age = 25
console.log(`姓名: ${name}, 年龄: ${age}`)重定向输出
const fs = require("fs")
// 重定向 stdout 到文件
const output = fs.createWriteStream("./stdout.log")
const errorOutput = fs.createWriteStream("./stderr.log")
const { Console } = require("console")
const logger = new Console({ stdout: output, stderr: errorOutput })
logger.log("这条信息会写入文件")
logger.error("这条错误信息会写入错误日志文件")process 对象
process 对象提供当前 Node.js 进程的信息和控制能力,是一个 EventEmitter 实例。
进程信息属性
| 属性 | 说明 | 示例 |
|---|---|---|
version | 当前 Node.js 的版本 | v20.10.0 |
versions | 当前 Node.js 的版本号以及依赖包 | { node: '20.10.0', v8: '...' } |
platform | 运行程序所在的平台系统 | darwin、win32、linux |
arch | 当前 CPU 的架构 | x64、arm64 |
pid | 当前进程的进程号 | 12345 |
ppid | 当前进程的父进程的进程号 | 12344 |
title | 进程名(可修改) | node |
argv | 命令行参数数组 | ['node', 'app.js', 'arg1'] |
execArgv | Node 可执行文件与脚本文件之间的命令行参数 | ['--require', 'dotenv'] |
execPath | 执行当前脚本的 Node 二进制文件的绝对路径 | /usr/local/bin/node |
env | 当前系统的环境变量 | { PATH: '...', HOME: '...' } |
cwd() | 当前工作目录 | /Users/project |
config | 编译当前 Node 执行文件的配置选项对象 | { ... } |
connected | 如果进程是通过 IPC 通道派生的,返回 true | true/false |
内存和资源属性
| 属性/方法 | 说明 |
|---|---|
memoryUsage() | 返回 Node 进程内存使用状况(字节) |
cpuUsage() | 返回 CPU 使用情况 |
resourceUsage() | 返回资源使用情况 |
uptime() | 返回 Node 已经运行的秒数 |
hrtime() | 高精度时间(纳秒级) |
进程信息示例
// 进程基本信息
console.log("Node.js 版本:", process.version)
console.log("平台:", process.platform)
console.log("CPU 架构:", process.arch)
console.log("进程 ID:", process.pid)
console.log("父进程 ID:", process.ppid)
console.log("当前工作目录:", process.cwd())
console.log("Node 可执行文件路径:", process.execPath)
// 详细版本信息
console.log("版本详情:", process.versions)
// {
// node: '20.10.0',
// v8: '11.3.244.8-node.12',
// uv: '1.46.0',
// zlib: '1.2.13.1-motley',
// ...
// }环境变量
// 所有环境变量
console.log(process.env)
// 常用环境变量
console.log("PATH:", process.env.PATH)
console.log("HOME:", process.env.HOME)
console.log("NODE_ENV:", process.env.NODE_ENV)
// 设置环境变量(仅当前进程)
process.env.MY_VAR = "my value"
// 判断环境
if (process.env.NODE_ENV === "production") {
console.log("生产环境")
} else if (process.env.NODE_ENV === "development") {
console.log("开发环境")
} else if (process.env.NODE_ENV === "test") {
console.log("测试环境")
}
// 跨平台环境变量设置
// 在 package.json 中:
// "scripts": {
// "dev": "cross-env NODE_ENV=development node app.js",
// "prod": "cross-env NODE_ENV=production node app.js"
// }命令行参数
// 命令行参数数组
console.log(process.argv)
// [0] - Node.js 可执行文件路径
// [1] - 当前脚本文件路径
// [2...] - 传递给脚本的参数
// 示例:node app.js --port 3000 --mode dev
const args = process.argv.slice(2) // 跳过前两个参数
console.log(args) // ['--port', '3000', '--mode', 'dev']
// 解析命令行参数
function parseArgs() {
const args = {}
let currentKey = null
process.argv.slice(2).forEach((arg) => {
if (arg.startsWith("--")) {
currentKey = arg.slice(2)
args[currentKey] = true
} else if (currentKey) {
args[currentKey] = arg
currentKey = null
}
})
return args
}
const options = parseArgs()
console.log(options) // { port: '3000', mode: 'dev' }使用第三方库解析参数:
// 推荐使用 commander 或 yargs
// npm install commander
const { program } = require("commander")
program
.option("-p, --port <number>", "端口号", 3000)
.option("-m, --mode <string>", "运行模式", "development")
.parse(process.argv)
const options = program.opts()
console.log("端口:", options.port)
console.log("模式:", options.mode)进程控制方法
| 方法 | 说明 |
|---|---|
exit([code]) | 使用指定的 code 结束进程 |
abort() | 立即终止进程(生成核心转储) |
chdir(directory) | 改变当前工作目录 |
setgid(id) | 设置进程的组 ID |
setuid(id) | 设置进程的用户 ID |
setegid(id) | 设置进程的有效组 ID |
seteuid(id) | 设置进程的有效用户 ID |
// 退出进程
process.exit(0) // 正常退出(0 表示成功)
process.exit(1) // 异常退出(非 0 表示失败)
// 退出前执行清理
process.on("exit", (code) => {
console.log(`进程退出,退出码: ${code}`)
// 注意:这里只能执行同步操作
})
// 改变工作目录
console.log("当前目录:", process.cwd())
process.chdir("/tmp")
console.log("新目录:", process.cwd())内存使用监控
// 获取内存使用情况
const usage = process.memoryUsage()
console.log(usage)
// {
// rss: 35635200, // 常驻集大小(Resident Set Size)
// heapTotal: 4751360, // 堆总大小
// heapUsed: 2746400, // 已使用的堆大小
// external: 1234567, // 外部内存使用(C++ 对象)
// arrayBuffers: 12345 // ArrayBuffer 内存使用
// }
// 格式化输出
function formatMemoryUsage() {
const used = process.memoryUsage()
return {
rss: `${Math.round(used.rss / 1024 / 1024)} MB`,
heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`,
heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`,
external: `${Math.round(used.external / 1024 / 1024)} MB`,
arrayBuffers: `${Math.round(used.arrayBuffers / 1024 / 1024)} MB`,
}
}
console.log(formatMemoryUsage())
// 定时监控内存
setInterval(() => {
const used = process.memoryUsage()
console.log(`内存使用: ${Math.round(used.heapUsed / 1024 / 1024)} MB`)
}, 60000)CPU 使用监控
// 获取 CPU 使用情况
const cpuUsage = process.cpuUsage()
console.log(cpuUsage)
// { user: 123456, system: 12345 }
// user - 用户态 CPU 时间(微秒)
// system - 内核态 CPU 时间(微秒)
// 计算差值
const start = process.cpuUsage()
// ... 执行一些操作
const end = process.cpuUsage(start)
console.log(`CPU 时间: 用户态 ${end.user}μs, 内核态 ${end.system}μs`)高精度时间
// 高精度时间(纳秒级)
const start = process.hrtime.bigint()
// 执行操作
for (let i = 0; i < 1000000; i++) {}
const end = process.hrtime.bigint()
console.log(`耗时: ${(end - start) / 1000000n} 毫秒`)
// 旧版 API(数组形式)
const startOld = process.hrtime()
// 执行操作
const endOld = process.hrtime(startOld)
console.log(`耗时: ${endOld[0]}秒 ${endOld[1]}纳秒`)事件监听
// 监听未捕获的异常
process.on("uncaughtException", (err, origin) => {
console.error("未捕获的异常:", err)
console.error("来源:", origin)
process.exit(1)
})
// 监听未处理的 Promise 拒绝
process.on("unhandledRejection", (reason, promise) => {
console.error("未处理的 Promise 拒绝:", reason)
// 建议: process.exit(1)
})
// 监听退出信号
process.on("SIGINT", () => {
console.log("收到 SIGINT 信号(Ctrl+C),正在退出...")
// 执行清理操作
process.exit(0)
})
process.on("SIGTERM", () => {
console.log("收到 SIGTERM 信号,正在退出...")
// 执行清理操作
process.exit(0)
})
// 监听警告
process.on("warning", (warning) => {
console.warn("警告名称:", warning.name)
console.warn("警告消息:", warning.message)
console.warn("堆栈:", warning.stack)
})
// 监听退出事件
process.on("exit", (code) => {
console.log(`进程即将退出,退出码: ${code}`)
// 注意:只能执行同步操作
})
// 监听多个信号
["SIGINT", "SIGTERM", "SIGUSR2"].forEach((signal) => {
process.on(signal, () => {
console.log(`收到 ${signal} 信号`)
})
})进程通信(IPC)
// 检查是否有 IPC 通道
if (process.connected) {
// 发送消息给父进程
process.send({ type: "ready", data: "子进程已就绪" })
// 接收父进程消息
process.on("message", (message) => {
console.log("收到父进程消息:", message)
})
}
// 断开 IPC 通道
process.disconnect()完整示例:命令行程序
// app.js - 命令行程序示例
const fs = require("fs")
const path = require("path")
// 获取命令行参数
const args = process.argv.slice(2)
if (args.length === 0) {
console.log("用法: node app.js <command> [options]")
console.log("命令:")
console.log(" info 显示进程信息")
console.log(" memory 显示内存使用")
console.log(" env 显示环境变量")
console.log(" cwd 显示当前目录")
process.exit(1)
}
const command = args[0]
switch (command) {
case "info":
console.log("进程信息:")
console.log(` PID: ${process.pid}`)
console.log(` Node 版本: ${process.version}`)
console.log(` 平台: ${process.platform}`)
console.log(` 架构: ${process.arch}`)
break
case "memory":
const used = process.memoryUsage()
console.log("内存使用:")
console.log(` RSS: ${Math.round(used.rss / 1024 / 1024)} MB`)
console.log(` 堆使用: ${Math.round(used.heapUsed / 1024 / 1024)} MB`)
break
case "env":
const key = args[1]
if (key) {
console.log(`${key} = ${process.env[key]}`)
} else {
console.log("环境变量:")
Object.keys(process.env).forEach((k) => {
console.log(` ${k}=${process.env[k]}`)
})
}
break
case "cwd":
console.log(`当前工作目录: ${process.cwd()}`)
break
default:
console.error(`未知命令: ${command}`)
process.exit(1)
}运行示例:
node app.js info
node app.js memory
node app.js env NODE_ENV
node app.js cwdBuffer 类
Buffer 是 Node.js 中处理二进制数据的核心类,用于在 TCP 流、文件系统操作等场景中处理原始数据。
Buffer 特点
- Buffer 类似于整数数组,但对应 V8 堆内存之外的一块原始内存
- Buffer 大小在创建时确定,无法更改
- Buffer 用于处理二进制数据流
- Buffer 在 Node.js 中全局可用,无需
require
创建 Buffer
// 方式一:分配指定大小的 Buffer(填充 0)
const buf1 = Buffer.alloc(10) // 10 字节,全部填充 0
console.log(buf1) // <Buffer 00 00 00 00 00 00 00 00 00 00>
// 方式二:分配但不初始化(更快但可能包含旧数据)
const buf2 = Buffer.allocUnsafe(10) // 10 字节,内容不确定
console.log(buf2) // <Buffer ...>
// 方式三:从字符串创建
const buf3 = Buffer.from("Hello World")
console.log(buf3) // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
// 指定编码
const buf4 = Buffer.from("你好", "utf8")
console.log(buf4) // <Buffer e4 bd a0 e5 a5 bd>
// 方式四:从数组创建
const buf5 = Buffer.from([1, 2, 3, 4, 5])
console.log(buf5) // <Buffer 01 02 03 04 05>
// 方式五:从另一个 Buffer 创建
const buf6 = Buffer.from(buf3)
console.log(buf6) // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
// 方式六:分配并填充
const buf7 = Buffer.alloc(10, "a")
console.log(buf7) // <Buffer 61 61 61 61 61 61 61 61 61 61>Buffer 编码
Node.js 支持的字符编码:
| 编码 | 说明 |
|---|---|
utf8 | UTF-8(默认) |
ascii | ASCII |
utf16le | UTF-16 Little Endian |
ucs2 | UTF-16 LE(utf16le 别名) |
base64 | Base64 编码 |
base64url | Base64 URL 安全编码 |
latin1 | Latin-1 (ISO-8859-1) |
binary | 二进制(latin1 别名) |
hex | 十六进制 |
// 不同编码的 Buffer
const str = "你好世界"
const utf8Buf = Buffer.from(str, "utf8")
console.log("UTF-8:", utf8Buf.toString("utf8"))
const base64Buf = Buffer.from(str, "utf8")
console.log("Base64:", base64Buf.toString("base64"))
const hexBuf = Buffer.from(str, "utf8")
console.log("Hex:", hexBuf.toString("hex"))读写 Buffer
const buf = Buffer.alloc(16)
// 写入数据
buf.write("Hello", "utf8") // 返回写入的字节数
console.log(buf.toString("utf8", 0, 5)) // "Hello"
// 写入不同偏移量
buf.write("World", 5, "utf8")
console.log(buf.toString("utf8", 0, 10)) // "HelloWorld"
// 读取单个字节
console.log(buf[0]) // 72 (ASCII 码 'H')
console.log(buf[1]) // 101 (ASCII 码 'e')
// 修改单个字节
buf[0] = 74 // 'J'
console.log(buf.toString("utf8", 0, 5)) // "Jello"Buffer 方法
// 拼接 Buffer
const buf1 = Buffer.from("Hello ")
const buf2 = Buffer.from("World")
const buf3 = Buffer.concat([buf1, buf2])
console.log(buf3.toString()) // "Hello World"
// 比较 Buffer
const a = Buffer.from("abc")
const b = Buffer.from("abd")
console.log(a.compare(b)) // -1 (a < b)
console.log(a.equals(b)) // false
// 查找
const buf = Buffer.from("Hello World")
console.log(buf.indexOf("World")) // 6
console.log(buf.indexOf("o")) // 4
console.log(buf.lastIndexOf("o")) // 7
// 包含
console.log(buf.includes("World")) // true
console.log(buf.includes("test")) // false
// 切片
const slice = buf.slice(0, 5)
console.log(slice.toString()) // "Hello"
// 复制
const copy = Buffer.alloc(5)
buf.copy(copy, 0, 0, 5)
console.log(copy.toString()) // "Hello"
// 转换为 JSON
const json = buf.toJSON()
console.log(json)
// { type: 'Buffer', data: [72, 101, 108, 108, 111, ...] }
// Buffer 长度
console.log(buf.length) // 11
console.log(Buffer.byteLength("你好")) // 6 (UTF-8 编码,每个汉字 3 字节)Buffer 与 TypedArray
// Buffer 与 TypedArray 共享内存
const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8])
const arr = new Int32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4)
console.log(arr) // Int32Array(2) [ 16909060, 135954332 ]
// 修改 TypedArray 会影响 Buffer
arr[0] = 0
console.log(buf) // <Buffer 00 00 00 00 05 06 07 08>Buffer 实用示例
// Base64 编码/解码
function base64Encode(str) {
return Buffer.from(str, "utf8").toString("base64")
}
function base64Decode(str) {
return Buffer.from(str, "base64").toString("utf8")
}
const encoded = base64Encode("Hello World")
console.log(encoded) // "SGVsbG8gV29ybGQ="
console.log(base64Decode(encoded)) // "Hello World"
// 十六进制转换
function toHex(str) {
return Buffer.from(str, "utf8").toString("hex")
}
function fromHex(hex) {
return Buffer.from(hex, "hex").toString("utf8")
}
console.log(toHex("Hello")) // "48656c6c6f"
console.log(fromHex("48656c6c6f")) // "Hello"
// Buffer 比较工具
function compareBuffers(buf1, buf2) {
if (buf1.equals(buf2)) {
return "Buffers 相同"
}
const diff = []
for (let i = 0; i < Math.max(buf1.length, buf2.length); i++) {
if (buf1[i] !== buf2[i]) {
diff.push({ index: i, buf1: buf1[i], buf2: buf2[i] })
}
}
return `Buffers 不同,差异位置: ${JSON.stringify(diff)}`
}global 和 globalThis
// global - Node.js 的全局对象
console.log(global === globalThis) // true (Node.js 12+)
// globalThis - ES2020 标准的全局对象
// 在浏览器中指向 window,在 Node.js 中指向 global
// 检测运行环境
if (typeof window === "undefined") {
console.log("运行在 Node.js 环境")
} else {
console.log("运行在浏览器环境")
}
// 全局变量(不推荐使用)
global.myGlobal = "这是一个全局变量"
console.log(global.myGlobal) // "这是一个全局变量"
// 最佳实践:避免使用 global
// 使用模块系统代替全局变量URL 和 URLSearchParams
Node.js 提供了符合 Web 标准的 URL API。
URL 对象
// 创建 URL 对象
const myUrl = new URL("https://example.com:8080/path/name?query=value#hash")
console.log(myUrl.href) // 完整 URL
console.log(myUrl.origin) // "https://example.com:8080"
console.log(myUrl.protocol) // "https:"
console.log(myUrl.host) // "example.com:8080"
console.log(myUrl.hostname) // "example.com"
console.log(myUrl.port) // "8080"
console.log(myUrl.pathname) // "/path/name"
console.log(myUrl.search) // "?query=value"
console.log(myUrl.hash) // "#hash"
// 修改 URL
myUrl.pathname = "/new/path"
myUrl.searchParams.set("key", "value")
console.log(myUrl.href) // "https://example.com:8080/new/path?query=value&key=value#hash"
// 构造 URL
const baseUrl = "https://example.com"
const relativePath = "/api/users"
const fullUrl = new URL(relativePath, baseUrl)
console.log(fullUrl.href) // "https://example.com/api/users"URLSearchParams
// 创建 URLSearchParams
const params = new URLSearchParams("query=value&key=value2")
// 添加参数
params.append("newKey", "newValue")
console.log(params.toString()) // "query=value&key=value2&newKey=newValue"
// 设置参数
params.set("query", "newValue")
console.log(params.toString()) // "query=newValue&key=value2&newKey=newValue"
// 获取参数
console.log(params.get("query")) // "newValue"
console.log(params.getAll("newKey")) // ["newValue"]
// 删除参数
params.delete("newKey")
// 检查参数
console.log(params.has("query")) // true
console.log(params.has("newKey")) // false
// 遍历参数
for (const [key, value] of params) {
console.log(`${key}: ${value}`)
}
// 转换为对象
const paramsObj = Object.fromEntries(params)
console.log(paramsObj) // { query: 'newValue', key: 'value2' }URL 解析工具
// 解析请求 URL
function parseRequestUrl(urlString) {
const url = new URL(urlString, "http://localhost")
return {
pathname: url.pathname,
query: Object.fromEntries(url.searchParams),
hash: url.hash,
}
}
console.log(parseRequestUrl("/api/users?id=1&name=zhangsan#section"))
// {
// pathname: '/api/users',
// query: { id: '1', name: 'zhangsan' },
// hash: '#section'
// }
// 构建 URL
function buildUrl(baseUrl, path, params = {}) {
const url = new URL(path, baseUrl)
Object.entries(params).forEach(([key, value]) => {
url.searchParams.set(key, value)
})
return url.href
}
console.log(buildUrl("https://api.example.com", "/users", { id: 1, name: "zhangsan" }))
// "https://api.example.com/users?id=1&name=zhangsan"TextEncoder 和 TextDecoder
用于文本编码和解码的 Web 标准 API。
// TextEncoder - 将字符串编码为 Uint8Array
const encoder = new TextEncoder()
const encoded = encoder.encode("你好世界")
console.log(encoded) // Uint8Array(12) [ 228, 189, 160, 229, 165, 189, ... ]
console.log(encoded.length) // 12 (UTF-8 编码)
// TextDecoder - 将 Uint8Array 解码为字符串
const decoder = new TextDecoder()
const decoded = decoder.decode(encoded)
console.log(decoded) // "你好世界"
// 编码为特定格式
const utf16Encoder = new TextEncoder()
const utf16Decoder = new TextDecoder("utf-16le")
// 流式解码
const streamDecoder = new TextDecoder("utf-8")
console.log(streamDecoder.decode(new Uint8Array([228, 189]), { stream: true }))
console.log(streamDecoder.decode(new Uint8Array([160]), { stream: true }))
console.log(streamDecoder.decode(new Uint8Array([]))) // 完成解码定时器函数
Node.js 提供了多种定时器函数,用于延迟执行代码。
定时器对比
| 定时器 | 说明 | 执行时机 |
|---|---|---|
setTimeout() | 延迟执行一次 | Timers 阶段 |
setInterval() | 周期性执行 | Timers 阶段 |
setImmediate() | 下一个事件循环立即执行 | Check 阶段 |
process.nextTick() | 当前操作完成后立即执行 | 微任务(优先级最高) |
setTimeout 和 setInterval
// setTimeout - 延迟执行
const timeoutId = setTimeout(() => {
console.log("1 秒后执行")
}, 1000)
// 取消定时器
clearTimeout(timeoutId)
// setInterval - 周期性执行
let count = 0
const intervalId = setInterval(() => {
count++
console.log(`第 ${count} 次执行`)
if (count >= 5) {
clearInterval(intervalId)
console.log("停止执行")
}
}, 1000)
// 传递参数
setTimeout(
(message, delay) => {
console.log(message, delay)
},
1000,
"Hello",
"1000ms"
)
// 延迟为 0 的 setTimeout
setTimeout(() => {
console.log("下一个事件循环执行")
}, 0)setImmediate
// setImmediate - 下一个 Check 阶段执行
setImmediate(() => {
console.log("setImmediate 执行")
})
// setImmediate vs setTimeout(0)
// 在 I/O 回调中,setImmediate 优先执行
const fs = require("fs")
fs.readFile(__filename, () => {
setTimeout(() => console.log("setTimeout"), 0)
setImmediate(() => console.log("setImmediate"))
})
// 输出顺序:setImmediate, setTimeoutprocess.nextTick
// process.nextTick - 当前操作完成后立即执行(微任务)
process.nextTick(() => {
console.log("nextTick 执行")
})
console.log("同步代码")
// 输出顺序:
// 1. 同步代码
// 2. nextTick 执行执行顺序
console.log("1. 同步代码开始")
setTimeout(() => console.log("2. setTimeout"), 0)
setImmediate(() => console.log("3. setImmediate"))
process.nextTick(() => console.log("4. nextTick"))
Promise.resolve().then(() => console.log("5. Promise"))
console.log("6. 同步代码结束")
// 输出顺序:
// 1. 同步代码开始
// 6. 同步代码结束
// 4. nextTick
// 5. Promise
// 2. setTimeout (或 3. setImmediate)
// 3. setImmediate (或 2. setTimeout)执行优先级:
同步代码 > process.nextTick > Promise > setTimeout/setImmediate定时器最佳实践
// ❌ 避免:长时间运行的定时器
setInterval(() => {
// 如果这个操作需要很长时间,会阻塞事件循环
heavyOperation()
}, 100)
// ✅ 推荐:递归 setTimeout
function scheduleTask() {
heavyOperation(() => {
setTimeout(scheduleTask, 100)
})
}
scheduleTask()
// ✅ 推荐:使用 AbortController 取消定时器
const controller = new AbortController()
const { signal } = controller
setTimeout(() => {
console.log("这个不会执行")
}, 1000, { signal })
controller.abort() // 取消定时器
// ✅ 推荐:封装为 Promise
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async function example() {
console.log("开始")
await delay(1000)
console.log("1 秒后")
}__dirname 和 __filename
这两个全局变量提供了当前模块的目录和文件路径信息。
基本用法
// __dirname - 当前模块所在的目录路径(绝对路径)
console.log(__dirname)
// 例如: /Users/username/project/src
// __filename - 当前模块文件的绝对路径
console.log(__filename)
// 例如: /Users/username/project/src/app.js路径操作
const path = require("path")
const fs = require("fs")
// 获取当前文件名
const fileName = path.basename(__filename)
console.log(`当前文件: ${fileName}`)
// 获取当前目录名
const dirName = path.basename(__dirname)
console.log(`当前目录: ${dirName}`)
// 获取文件扩展名
const ext = path.extname(__filename)
console.log(`扩展名: ${ext}`)
// 拼接路径
const filePath = path.join(__dirname, "data", "config.json")
console.log(`配置文件路径: ${filePath}`)
// 解析路径
const parsed = path.parse(__filename)
console.log(parsed)
// {
// root: '/',
// dir: '/Users/username/project/src',
// base: 'app.js',
// ext: '.js',
// name: 'app'
// }读取文件
const fs = require("fs")
const path = require("path")
// 读取当前目录下的文件
const filePath = path.join(__dirname, "data.txt")
fs.readFile(filePath, "utf8", (err, data) => {
if (err) {
console.error(err)
return
}
console.log(data)
})
// 读取上级目录的文件
const configPath = path.join(__dirname, "..", "config.json")
fs.readFile(configPath, "utf8", (err, data) => {
if (err) throw err
console.log(JSON.parse(data))
})__dirname vs process.cwd
// __dirname - 当前文件所在目录
console.log("__dirname:", __dirname)
// process.cwd() - 执行 node 命令的目录
console.log("cwd:", process.cwd())
// 示例
// 目录结构:
// /Users/project/
// ├── app.js
// └── src/
// └── index.js
// 在 src/index.js 中:
// __dirname: /Users/project/src
// process.cwd(): /Users/project(如果在 project 目录执行 node src/index.js)ES Modules 中的替代方案
// ES Modules 中 __dirname 和 __filename 不可用
// 使用 import.meta.url 替代
import { fileURLToPath } from "url"
import { dirname, basename } from "path"
// 获取当前文件的绝对路径
const __filename = fileURLToPath(import.meta.url)
console.log(__filename)
// 获取当前目录
const __dirname = dirname(__filename)
console.log(__dirname)
// 使用方式与 CommonJS 相同
const configPath = path.join(__dirname, "config.json")require 和模块系统
Node.js 使用 CommonJS 模块系统,require 是引入模块的核心函数。
模块系统架构
┌─────────────────────────────────────────────────────────────┐
│ CommonJS 模块系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 导出方式: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ exports.xxx = value # 导出多个属性 │ │
│ │ module.exports = value # 导出单个值或对象 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 导入方式: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ const module = require('module-name') │ │
│ │ const { xxx, yyy } = require('./local-module') │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘require 函数
基本用法
// 引入内置模块
const fs = require("fs")
const http = require("http")
const path = require("path")
const crypto = require("crypto")
// 引入本地模块(相对路径)
const utils = require("./utils")
const config = require("./config.json")
const db = require("../database/connection")
// 引入 npm 包
const express = require("express")
const lodash = require("lodash")
const moment = require("moment")
// 解构导入
const { readFile, writeFile } = require("fs")
const { join, resolve } = require("path")模块查找顺序
┌─────────────────────────────────────────────────────────────┐
│ 模块查找顺序 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 内置模块(如 fs、http、path) │
│ ↓ │
│ 2. 相对路径(./ 或 ../) │
│ ↓ │
│ 3. 绝对路径(/) │
│ ↓ │
│ 4. node_modules 目录 │
│ ↓ │
│ 5. 向上查找 node_modules(直到根目录) │
│ ↓ │
│ 6. 全局 node_modules │
│ ↓ │
│ 7. NODE_PATH 环境变量指定的路径 │
│ │
└─────────────────────────────────────────────────────────────┘// 1. 内置模块 - 直接返回
const fs = require("fs")
// 2. 相对路径
const utils = require("./utils") // 查找 ./utils.js 或 ./utils/index.js
// 3. 绝对路径
const config = require("/etc/config")
// 4. node_modules
const express = require("express") // 查找 node_modules/express
// 查找过程:
// ./node_modules/express
// ../node_modules/express
// ../../node_modules/express
// ... 直到根目录模块缓存
// 模块只会被加载一次,后续 require 会返回缓存
const module1 = require("./module")
const module2 = require("./module")
console.log(module1 === module2) // true
// 检查缓存
console.log(require.cache)
// 删除缓存(用于热重载)
delete require.cache[require.resolve("./module")]
// 清除所有缓存
Object.keys(require.cache).forEach((key) => {
delete require.cache[key]
})动态 require
// 根据条件动态加载模块
const env = process.env.NODE_ENV || "development"
const config = require(`./config/${env}`)
// 使用变量(需要完整路径)
const path = require("path")
const moduleName = process.argv[2]
const modulePath = path.join(__dirname, "modules", `${moduleName}.js`)
if (fs.existsSync(modulePath)) {
const module = require(modulePath)
module.run()
} else {
console.error(`模块 ${moduleName} 不存在`)
}
// 批量加载模块
const modules = {}
const moduleDir = path.join(__dirname, "modules")
fs.readdirSync(moduleDir).forEach((file) => {
if (file.endsWith(".js")) {
const name = path.basename(file, ".js")
modules[name] = require(path.join(moduleDir, file))
}
})module 对象
module 对象表示当前模块,包含模块的元信息。
module 属性
| 属性 | 说明 |
|---|---|
id | 模块的标识符,通常是完全解析后的文件名 |
filename | 模块的绝对路径 |
loaded | 模块是否已加载完毕 |
parent | 首次加载该模块的模块(已废弃) |
children | 该模块引用的其他模块 |
paths | 模块查找路径数组 |
exports | 模块导出的对象 |
path | 模块所在目录 |
// 当前模块信息
console.log("模块 ID:", module.id)
console.log("文件路径:", module.filename)
console.log("已加载:", module.loaded)
console.log("查找路径:", module.paths)
// 子模块
console.log("子模块:", module.children)module.exports 和 exports
导出单个值
// math.js - 导出函数
function add(a, b) {
return a + b
}
module.exports = add
// app.js
const add = require("./math")
console.log(add(1, 2)) // 3导出对象
// math.js - 导出多个函数
function add(a, b) {
return a + b
}
function subtract(a, b) {
return a - b
}
function multiply(a, b) {
return a * b
}
// 方式一:导出对象
module.exports = {
add,
subtract,
multiply,
}
// 方式二:直接添加到 exports
module.exports.add = add
module.exports.subtract = subtract
module.exports.multiply = multiply
// app.js
const math = require("./math")
console.log(math.add(1, 2))
console.log(math.subtract(5, 3))使用 exports
// utils.js - 使用 exports
exports.add = function (a, b) {
return a + b
}
exports.multiply = function (a, b) {
return a * b
}
// app.js - 解构导入
const { add, multiply } = require("./utils")
console.log(add(1, 2))
console.log(multiply(2, 3))导出类
// person.js
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
sayHello() {
console.log(`Hello, I'm ${this.name}, ${this.age} years old.`)
}
}
module.exports = Person
// app.js
const Person = require("./person")
const person = new Person("张三", 25)
person.sayHello()exports vs module.exports
// exports 和 module.exports 初始指向同一个对象
console.log(exports === module.exports) // true
// ❌ 错误:直接给 exports 赋值会断开引用
exports = { add: add } // 不会生效!
// ✅ 正确方式一:使用 module.exports
module.exports = { add: add }
// ✅ 正确方式二:给 exports 添加属性
exports.add = add
// ✅ 正确方式三:使用 module.exports 添加属性
module.exports.add = add最佳实践:
- 导出单个值(函数、类):使用
module.exports = value - 导出多个值:使用
module.exports = { a, b, c }或exports.a = a
模块类型
JavaScript 模块
// math.js
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
}
// app.js
const math = require("./math")
console.log(math.add(1, 2))JSON 模块
// config.json
{
"port": 3000,
"host": "localhost",
"database": {
"host": "localhost",
"port": 5432
}
}
// app.js
const config = require("./config.json")
console.log(config.port) // 3000
console.log(config.database.host) // localhost
// 注意:JSON 文件会被自动解析为 JavaScript 对象
// 且会被缓存,修改文件后需要清除缓存才能重新加载Node.js 原生模块
// 编译后的 C++ 插件(.node 文件)
const addon = require("./build/Release/addon.node")
// 通常用于性能敏感的操作
// 如:图像处理、加密算法等目录作为模块
// 目录结构
// my-module/
// ├── index.js
// ├── package.json
// └── lib/
// └── helper.js
// 当 require('./my-module') 时,Node.js 会:
// 1. 查找 my-module/package.json 的 main 字段
// 2. 如果没有 main 字段,查找 my-module/index.js
// my-module/package.json
{
"name": "my-module",
"main": "index.js"
}
// my-module/index.js
const helper = require("./lib/helper")
module.exports = {
doSomething: helper.doSomething,
}
// app.js
const myModule = require("./my-module")
myModule.doSomething()ES Modules
Node.js 也支持 ES Modules(ESM),使用 import/export 语法。
启用 ESM
// 方式一:使用 .mjs 扩展名
// file.mjs
// 方式二:在 package.json 中设置 type
// package.json
{
"type": "module"
}
// 方式三:使用 --input-type=module 参数
node --input-type=module --eval "import fs from 'fs'; console.log(fs);"ESM 语法
// math.mjs
export function add(a, b) {
return a + b
}
export function subtract(a, b) {
return a - b
}
// 默认导出
export default class Calculator {
add(a, b) {
return a + b
}
}
// app.mjs
import Calculator, { add, subtract } from "./math.mjs"
console.log(add(1, 2))
console.log(subtract(5, 3))
const calc = new Calculator()
console.log(calc.add(3, 4))
// 导入所有
import * as math from "./math.mjs"
console.log(math.add(1, 2))CommonJS 和 ESM 互操作
// 在 ESM 中导入 CommonJS 模块
import fs from "fs" // 可行
import { add } from "./commonjs-module.cjs" // 可行
// 在 CommonJS 中导入 ESM 模块(需要动态导入)
// app.cjs
async function main() {
const { add } = await import("./math.mjs")
console.log(add(1, 2))
}
main()实战视角:Webpack 源码中的 ES6+ 语法特性
在 Node.js 项目中,ES6+ 语法特性被广泛使用。以下以 Webpack 及其依赖模块的真实源码为例,展示各语法特性在实际工程中的应用方式。
语法特性应用实例
| 语法特性 | 说明 | Webpack 源码示例 |
|---|---|---|
| 变量声明 | let 具有块级作用域,const 声明不可变绑定 | let debugId = 1000<br>const Compiler = require('./Compiler') |
| 模板字符串 | 字符串的简洁拼接,可内嵌表达式与变量 | let message = `* ${m.identifier()}`<br>const ma = `${a.message}` |
| 箭头函数 | 省略 function 关键字的函数定义,词法绑定 this | const exportPlugins = (obj, mappings) => {} |
| 解构赋值 | 从目标数组或对象中提取特定值 | const { SyncHook } = require('tapable') |
| rest 参数与扩展运算符 | rest 将多余参数收集为数组,扩展运算符将数组展开为参数,二者互逆 | function(name, ...args) {<br> this.hooks[name.replace(/**/)].call(...args)<br>} |
| Symbol | 生成独一无二的值,永不重复,适合作为内部标识 | const MAYBEEND = Symbol('maybeEnd')<br>const WRITING = Symbol('writing') |
| Set | 存储任意类型值且保证唯一性的数据结构 | const queue = new Set(this.groupsIterable)<br>const chunksProcessed = new Set() |
| Map | 哈希结构的键值对集合,键可为任意类型 | const fileTs = (compiler.fileTimestamps = new Map()) |
| Promise | 管理异步状态的对象,在某个时刻回调返回异步执行结果 | return new Promise((resolve, reject) => {<br> require('fs').readFile(filename, (err, content) => {<br> try { var update = JSON.parse(content) }<br> catch (e) { return reject(e) }<br> resolve(update)<br> })<br>}) |
| for...of | 遍历可迭代对象的所有成员 | for (const dependency of module.dependencies) {} |
除上述特性外,Webpack 源码中还大量使用了 Class、Async Function、Generator Function 等特性。这些语法特性在实际工程中并非孤立使用,而是相互配合,共同提升代码的可读性与可维护性。
参考学习资料:
文件系统基础
fs 模块提供文件系统操作功能,是 Node.js 最常用的核心模块之一。
fs 模块概述
const fs = require("fs")
// fs 模块提供三种 API 风格:
// 1. 异步回调 API:fs.readFile(path, callback)
// 2. 同步 API:fs.readFileSync(path)
// 3. Promise API:fs.promises.readFile(path)异步文件操作
读取文件
const fs = require("fs")
// 异步读取文件
fs.readFile("example.txt", "utf8", (err, data) => {
if (err) {
console.error("读取文件失败:", err)
return
}
console.log("文件内容:", data)
})
// 不指定编码,返回 Buffer
fs.readFile("example.txt", (err, data) => {
if (err) {
console.error("读取文件失败:", err)
return
}
console.log("文件内容(Buffer):", data)
console.log("转换为字符串:", data.toString("utf8"))
})
// 读取大文件(使用流)
const readStream = fs.createReadStream("large-file.txt", "utf8")
let content = ""
readStream.on("data", (chunk) => {
content += chunk
})
readStream.on("end", () => {
console.log("文件读取完成:", content.length)
})
readStream.on("error", (err) => {
console.error("读取错误:", err)
})写入文件
const fs = require("fs")
// 异步写入文件(覆盖)
fs.writeFile("output.txt", "Hello Node.js!", "utf8", (err) => {
if (err) {
console.error("写入文件失败:", err)
return
}
console.log("文件写入成功")
})
// 追加内容
fs.appendFile("output.txt", "\n追加的内容", "utf8", (err) => {
if (err) {
console.error("追加文件失败:", err)
return
}
console.log("内容追加成功")
})
// 使用流写入大文件
const writeStream = fs.createWriteStream("large-output.txt")
for (let i = 0; i < 100000; i++) {
writeStream.write(`这是第 ${i} 行内容\n`)
}
writeStream.end()
writeStream.on("finish", () => {
console.log("写入完成")
})检查文件状态
const fs = require("fs")
// 检查文件是否存在
fs.access("example.txt", fs.constants.F_OK, (err) => {
if (err) {
console.log("文件不存在")
} else {
console.log("文件存在")
}
})
// 检查文件权限
fs.access("example.txt", fs.constants.R_OK | fs.constants.W_OK, (err) => {
if (err) {
console.log("文件不可读写")
} else {
console.log("文件可读写")
}
})
// 获取文件信息
fs.stat("example.txt", (err, stats) => {
if (err) {
console.error(err)
return
}
console.log("文件大小:", stats.size, "字节")
console.log("创建时间:", stats.birthtime)
console.log("修改时间:", stats.mtime)
console.log("访问时间:", stats.atime)
console.log("是文件:", stats.isFile())
console.log("是目录:", stats.isDirectory())
console.log("是符号链接:", stats.isSymbolicLink())
// 文件权限
console.log("权限:", stats.mode.toString(8))
})权限常量
const fs = require("fs")
// 文件存在性
fs.constants.F_OK // 文件是否存在
// 文件权限
fs.constants.R_OK // 可读
fs.constants.W_OK // 可写
fs.constants.X_OK // 可执行
// 组合使用
fs.access("file.txt", fs.constants.R_OK | fs.constants.W_OK, (err) => {
if (err) {
console.log("文件不可读写")
} else {
console.log("文件可读写")
}
})同步文件操作
同步操作会阻塞 Node.js 事件循环,通常不推荐使用,但在某些场景下(如启动脚本)可能有用。
const fs = require("fs")
// 同步读取文件
try {
const data = fs.readFileSync("example.txt", "utf8")
console.log("文件内容:", data)
} catch (err) {
console.error("读取文件失败:", err)
}
// 同步写入文件
try {
fs.writeFileSync("output.txt", "Hello Node.js!", "utf8")
console.log("文件写入成功")
} catch (err) {
console.error("写入文件失败:", err)
}
// 同步追加
try {
fs.appendFileSync("output.txt", "\n追加的内容", "utf8")
console.log("内容追加成功")
} catch (err) {
console.error("追加文件失败:", err)
}
// 同步检查文件
try {
fs.accessSync("example.txt", fs.constants.F_OK)
console.log("文件存在")
} catch (err) {
console.log("文件不存在")
}
// 同步获取文件信息
try {
const stats = fs.statSync("example.txt")
console.log("文件大小:", stats.size)
} catch (err) {
console.error(err)
}Promise API
Node.js 10+ 提供 Promise 版本的 fs API。
const fs = require("fs").promises
// 或
const { promises: fs } = require("fs")
// 读取文件
async function readFile() {
try {
const data = await fs.readFile("example.txt", "utf8")
console.log(data)
} catch (err) {
console.error("读取失败:", err)
}
}
// 写入文件
async function writeFile() {
try {
await fs.writeFile("output.txt", "Hello Node.js!", "utf8")
console.log("写入成功")
} catch (err) {
console.error("写入失败:", err)
}
}
// 追加内容
async function appendFile() {
try {
await fs.appendFile("output.txt", "\n追加的内容", "utf8")
console.log("追加成功")
} catch (err) {
console.error("追加失败:", err)
}
}
// 检查文件
async function checkFile() {
try {
await fs.access("example.txt", fs.constants.F_OK)
console.log("文件存在")
} catch {
console.log("文件不存在")
}
}
// 获取文件信息
async function getStats() {
try {
const stats = await fs.stat("example.txt")
console.log("文件大小:", stats.size)
console.log("是文件:", stats.isFile())
} catch (err) {
console.error(err)
}
}
// 复制文件
async function copyFile() {
try {
await fs.copyFile("source.txt", "target.txt")
console.log("复制成功")
} catch (err) {
console.error("复制失败:", err)
}
}
// 重命名/移动文件
async function renameFile() {
try {
await fs.rename("old-name.txt", "new-name.txt")
console.log("重命名成功")
} catch (err) {
console.error("重命名失败:", err)
}
}
// 删除文件
async function deleteFile() {
try {
await fs.unlink("file-to-delete.txt")
console.log("删除成功")
} catch (err) {
console.error("删除失败:", err)
}
}目录操作
创建目录
const fs = require("fs")
// 异步创建目录
fs.mkdir("newDir", (err) => {
if (err) {
console.error("创建目录失败:", err)
return
}
console.log("目录创建成功")
})
// 创建嵌套目录(需要 recursive 选项)
fs.mkdir("path/to/dir", { recursive: true }, (err) => {
if (err) {
console.error("创建目录失败:", err)
return
}
console.log("目录创建成功")
})
// 使用 Promise API
async function createDir() {
try {
await fs.promises.mkdir("path/to/dir", { recursive: true })
console.log("目录创建成功")
} catch (err) {
console.error("创建失败:", err)
}
}读取目录
const fs = require("fs")
// 异步读取目录
fs.readdir(".", (err, files) => {
if (err) {
console.error("读取目录失败:", err)
return
}
console.log("目录内容:", files)
})
// 读取目录(包含文件类型)
fs.readdir(".", { withFileTypes: true }, (err, files) => {
if (err) {
console.error("读取目录失败:", err)
return
}
files.forEach((file) => {
const type = file.isDirectory() ? "目录" : "文件"
console.log(`${type}: ${file.name}`)
})
})
// 使用 Promise API
async function readDir() {
try {
const files = await fs.promises.readdir(".", { withFileTypes: true })
files.forEach((file) => {
console.log(`${file.isDirectory() ? "📁" : "📄"} ${file.name}`)
})
} catch (err) {
console.error(err)
}
}删除目录
const fs = require("fs")
// 删除空目录
fs.rmdir("emptyDir", (err) => {
if (err) {
console.error("删除目录失败:", err)
return
}
console.log("目录删除成功")
})
// 删除目录及其内容
fs.rm("dir", { recursive: true }, (err) => {
if (err) {
console.error("删除目录失败:", err)
return
}
console.log("目录删除成功")
})
// 使用 Promise API
async function removeDir() {
try {
await fs.promises.rm("dir", { recursive: true, force: true })
console.log("删除成功")
} catch (err) {
console.error("删除失败:", err)
}
}文件操作完整示例
文件复制工具
const fs = require("fs")
const path = require("path")
// 方式一:回调方式
function copyFile(source, target, callback) {
fs.readFile(source, (err, data) => {
if (err) {
callback(err)
return
}
fs.writeFile(target, data, (err) => {
if (err) {
callback(err)
return
}
callback(null)
})
})
}
// 方式二:使用流(推荐大文件)
function copyFileStream(source, target, callback) {
const readStream = fs.createReadStream(source)
const writeStream = fs.createWriteStream(target)
readStream.on("error", callback)
writeStream.on("error", callback)
writeStream.on("finish", () => callback(null))
readStream.pipe(writeStream)
}
// 方式三:使用 Promise API
async function copyFileAsync(source, target) {
const data = await fs.promises.readFile(source)
await fs.promises.writeFile(target, data)
}
// 方式四:使用 copyFile
async function copyFileDirect(source, target) {
await fs.promises.copyFile(source, target)
}
// 使用示例
copyFile("source.txt", "target.txt", (err) => {
if (err) console.error(err)
else console.log("复制成功")
})目录遍历
const fs = require("fs")
const path = require("path")
// 同步遍历目录
function walkDir(dir, callback) {
const files = fs.readdirSync(dir)
files.forEach((file) => {
const filePath = path.join(dir, file)
const stats = fs.statSync(filePath)
if (stats.isDirectory()) {
walkDir(filePath, callback) // 递归遍历
} else {
callback(filePath)
}
})
}
// 异步遍历目录
async function walkDirAsync(dir, callback) {
const files = await fs.promises.readdir(dir, { withFileTypes: true })
for (const file of files) {
const filePath = path.join(dir, file.name)
if (file.isDirectory()) {
await walkDirAsync(filePath, callback)
} else {
await callback(filePath)
}
}
}
// 使用示例
walkDir("./src", (filePath) => {
console.log(filePath)
})
walkDirAsync("./src", async (filePath) => {
console.log(filePath)
})查找文件
const fs = require("fs")
const path = require("path")
// 查找特定扩展名的文件
function findFilesByExt(dir, ext, fileList = []) {
const files = fs.readdirSync(dir)
files.forEach((file) => {
const filePath = path.join(dir, file)
const stats = fs.statSync(filePath)
if (stats.isDirectory()) {
findFilesByExt(filePath, ext, fileList)
} else if (path.extname(file) === ext) {
fileList.push(filePath)
}
})
return fileList
}
// 使用示例
const jsFiles = findFilesByExt("./src", ".js")
console.log(jsFiles)命令行程序示例
// cli.js - 文件管理命令行工具
const fs = require("fs")
const path = require("path")
// 获取命令行参数
const args = process.argv.slice(2)
if (args.length === 0) {
console.log(`
文件管理工具
用法: node cli.js <command> [arguments]
命令:
read <file> 读取文件内容
write <file> <text> 写入文件
append <file> <text> 追加内容
list <dir> 列出目录
mkdir <dir> 创建目录
remove <path> 删除文件或目录
copy <src> <dest> 复制文件
info <file> 显示文件信息
`)
process.exit(0)
}
const command = args[0]
async function main() {
try {
switch (command) {
case "read": {
const file = args[1]
if (!file) throw new Error("请指定文件")
const data = await fs.promises.readFile(file, "utf8")
console.log(data)
break
}
case "write": {
const file = args[1]
const text = args[2] || ""
if (!file) throw new Error("请指定文件")
await fs.promises.writeFile(file, text, "utf8")
console.log(`已写入 ${file}`)
break
}
case "append": {
const file = args[1]
const text = args[2] || ""
if (!file) throw new Error("请指定文件")
await fs.promises.appendFile(file, text + "\n", "utf8")
console.log(`已追加到 ${file}`)
break
}
case "list": {
const dir = args[1] || "."
const files = await fs.promises.readdir(dir, { withFileTypes: true })
files.forEach((file) => {
const icon = file.isDirectory() ? "📁" : "📄"
console.log(`${icon} ${file.name}`)
})
break
}
case "mkdir": {
const dir = args[1]
if (!dir) throw new Error("请指定目录")
await fs.promises.mkdir(dir, { recursive: true })
console.log(`已创建目录 ${dir}`)
break
}
case "remove": {
const target = args[1]
if (!target) throw new Error("请指定路径")
const stats = await fs.promises.stat(target)
if (stats.isDirectory()) {
await fs.promises.rm(target, { recursive: true })
} else {
await fs.promises.unlink(target)
}
console.log(`已删除 ${target}`)
break
}
case "copy": {
const src = args[1]
const dest = args[2]
if (!src || !dest) throw new Error("请指定源文件和目标文件")
await fs.promises.copyFile(src, dest)
console.log(`已复制 ${src} -> ${dest}`)
break
}
case "info": {
const file = args[1]
if (!file) throw new Error("请指定文件")
const stats = await fs.promises.stat(file)
console.log(`路径: ${path.resolve(file)}`)
console.log(`大小: ${stats.size} 字节`)
console.log(`创建时间: ${stats.birthtime}`)
console.log(`修改时间: ${stats.mtime}`)
console.log(`类型: ${stats.isDirectory() ? "目录" : "文件"}`)
break
}
default:
throw new Error(`未知命令: ${command}`)
}
} catch (err) {
console.error(`错误: ${err.message}`)
process.exit(1)
}
}
main()使用示例:
# 显示帮助
node cli.js
# 读取文件
node cli.js read example.txt
# 写入文件
node cli.js write output.txt "Hello World"
# 追加内容
node cli.js append log.txt "新的日志行"
# 列出目录
node cli.js list .
node cli.js list /path/to/dir
# 创建目录
node cli.js mkdir new-folder
# 删除文件
node cli.js remove unwanted.txt
# 复制文件
node cli.js copy source.txt backup.txt
# 显示文件信息
node cli.js info example.txt常见问题解答
全局对象相关
Q: __dirname 和 process.cwd 有什么区别?
// __dirname - 当前文件所在目录的绝对路径(模块级)
console.log(__dirname) // /Users/project/src
// process.cwd() - 执行 node 命令的目录(进程级)
console.log(process.cwd()) // /Users/project
// 示例
// 目录结构:
// /Users/project/
// └── src/
// └── app.js
// 在 /Users/project 执行:node src/app.js
// __dirname = /Users/project/src
// process.cwd() = /Users/project
// 最佳实践:
// - 使用 __dirname 引用相对于模块的文件
// - 使用 process.cwd() 引用相对于项目根目录的文件Q: 如何安全地使用环境变量?
// ❌ 不安全:直接使用可能不存在的环境变量
const dbHost = process.env.DB_HOST // 可能是 undefined
// ✅ 安全:提供默认值
const dbHost = process.env.DB_HOST || "localhost"
// ✅ 更安全:验证必需的环境变量
function getEnv(key, defaultValue) {
const value = process.env[key]
if (value === undefined) {
if (defaultValue !== undefined) {
return defaultValue
}
throw new Error(`缺少必需的环境变量: ${key}`)
}
return value
}
const config = {
port: parseInt(getEnv("PORT", "3000"), 10),
dbHost: getEnv("DB_HOST"),
dbPort: parseInt(getEnv("DB_PORT", "5432"), 10),
}
// ✅ 推荐:使用 dotenv 管理环境变量
// npm install dotenv
require("dotenv").config()
// 或使用 convict 进行配置验证
// npm install convictQ: 如何正确处理未捕获的异常?
// 全局异常处理
process.on("uncaughtException", (err, origin) => {
console.error("未捕获的异常:", err)
console.error("来源:", origin)
// 记录日志
// logger.error(err)
// 执行必要的清理操作
// ...
// 建议退出进程
process.exit(1)
})
// 处理未处理的 Promise 拒绝
process.on("unhandledRejection", (reason, promise) => {
console.error("未处理的 Promise 拒绝:", reason)
// Node.js 15+ 默认会以非零退出码终止进程
// process.exit(1)
})
// 最佳实践:
// 1. 在代码中正确处理所有可能的错误
// 2. 使用 try-catch 或 .catch() 处理异步错误
// 3. 使用 PM2 等进程管理器自动重启模块系统相关
Q: require 和 import 有什么区别?
// CommonJS (require)
// 1. 同步加载
// 2. 运行时加载
// 3. 可以动态加载
// 4. 导入的是值的拷贝
const fs = require("fs")
const modulePath = process.env.NODE_ENV === "prod" ? "./prod" : "./dev"
const config = require(modulePath) // 动态加载
// ES Modules (import)
// 1. 异步加载
// 2. 编译时加载(静态分析)
// 3. 必须在顶层使用
// 4. 导入的是值的引用
import fs from "fs"
import { readFile } from "fs"
// 动态导入
const modulePath = "./module.js"
const module = await import(modulePath)
// 建议:
// - 新项目使用 ES Modules
// - 需要兼容旧代码时使用 CommonJSQ: 如何实现模块热重载?
// 开发环境热重载
function requireUncached(module) {
delete require.cache[require.resolve(module)]
return require(module)
}
// 使用示例
function loadConfig() {
return requireUncached("./config")
}
// 定时重载
setInterval(() => {
const config = loadConfig()
console.log("配置已重载:", config)
}, 5000)
// 或使用 nodemon 自动重启
// npm install -g nodemon
// nodemon app.js文件系统相关
Q: 如何处理大文件?
const fs = require("fs")
const path = require("path")
// ❌ 错误:一次性读取大文件到内存
fs.readFile("large-file.txt", (err, data) => {
// 可能导致内存不足
})
// ✅ 正确:使用流处理
function processLargeFile(inputPath, outputPath) {
const readStream = fs.createReadStream(inputPath, { highWaterMark: 64 * 1024 })
const writeStream = fs.createWriteStream(outputPath)
readStream.on("data", (chunk) => {
// 处理每个数据块
const processed = chunk.toString().toUpperCase()
writeStream.write(processed)
})
readStream.on("end", () => {
writeStream.end()
console.log("处理完成")
})
readStream.on("error", (err) => console.error("读取错误:", err))
writeStream.on("error", (err) => console.error("写入错误:", err))
}
// 使用管道
function copyLargeFile(src, dest) {
fs.createReadStream(src)
.pipe(fs.createWriteStream(dest))
.on("finish", () => console.log("复制完成"))
}Q: 如何处理文件编码问题?
const fs = require("fs")
const iconv = require("iconv-lite") // npm install iconv-lite
// 读取非 UTF-8 编码文件
function readFileWithEncoding(file, encoding = "utf8") {
const buffer = fs.readFileSync(file)
if (encoding === "utf8") {
return buffer.toString("utf8")
}
// 使用 iconv-lite 处理其他编码
return iconv.decode(buffer, encoding)
}
// 写入指定编码
function writeFileWithEncoding(file, content, encoding = "utf8") {
if (encoding === "utf8") {
fs.writeFileSync(file, content, "utf8")
} else {
const buffer = iconv.encode(content, encoding)
fs.writeFileSync(file, buffer)
}
}
// 常见编码
// GBK: 中国大陆
// BIG5: 繁体中文
// Shift_JIS: 日文
// EUC-KR: 韩文Q: 如何递归创建目录?
const fs = require("fs")
const path = require("path")
// Node.js 10.12+ 支持 recursive 选项
fs.mkdir("path/to/deep/dir", { recursive: true }, (err) => {
if (err) console.error(err)
else console.log("目录创建成功")
})
// 或使用 Promise API
async function ensureDir(dir) {
await fs.promises.mkdir(dir, { recursive: true })
}
// 兼容旧版本
function ensureDirSync(dir) {
if (fs.existsSync(dir)) return
const parent = path.dirname(dir)
if (!fs.existsSync(parent)) {
ensureDirSync(parent)
}
fs.mkdirSync(dir)
}性能优化
Q: 如何提高文件操作性能?
const fs = require("fs")
// 1. 使用异步操作避免阻塞
// ❌ 同步操作
const data = fs.readFileSync("file.txt", "utf8")
// ✅ 异步操作
fs.readFile("file.txt", "utf8", (err, data) => {
// ...
})
// 2. 使用流处理大文件
const stream = fs.createReadStream("large-file.txt")
// 3. 并发控制
async function processFiles(files) {
const limit = 10 // 并发限制
const results = []
for (let i = 0; i < files.length; i += limit) {
const batch = files.slice(i, i + limit)
const batchResults = await Promise.all(
batch.map((file) => fs.promises.readFile(file, "utf8"))
)
results.push(...batchResults)
}
return results
}
// 4. 使用缓存
const cache = new Map()
async function readFileCached(file) {
if (cache.has(file)) {
return cache.get(file)
}
const data = await fs.promises.readFile(file, "utf8")
cache.set(file, data)
return data
}案例实战:命令行动画龟兔赛跑
本节通过实现一个命令行动画——龟兔赛跑,综合演示 ES6+ 语法特性(const/let、箭头函数、模板字符串、Promise、Class、解构赋值、rest 参数、Proxy 等)在 Node.js 实际编程中的应用差异。
需求分析
实现一个在终端中实时刷新的龟兔赛跑动画,规则如下:
- 兔子速度为乌龟的 3 倍
- 兔子在跑到 42 米处停下休息
- 乌龟匀速前进,最终越过终点获胜
赛道状态演变:
起始状态: 兔子乌龟|..............................》
兔子领先: |......乌龟...................兔子.....》
乌龟追上: |.........................兔子乌龟.....》
乌龟获胜: |.........................兔子.....》乌龟函数式实现
核心思路:每隔固定时间间隔,根据龟兔当前距离计算赛道字符串,刷新终端输出。
// 声明比赛队员与赛道参数
const rabbit = '兔子'
const turtle = '乌龟'
const start = '|'
const end = '》'
const pad = '.'
const speed = 1 // 1 米/150 毫秒
const steps = 50 // 赛道总长 50 米
const stopAt = 42 // 兔子在 42 米处停下
let stoped = false // 兔子是否已停下
let t = 0 // 轮询计数
// 计算兔子距终点距离
const getRabbitLastSteps = () => steps - t * speed - t * speed * 3
// 计算乌龟距终点距离
const getTurtleLastSteps = () => steps - t * speed
// 计算龟兔间距
const getGapSteps = () => stopAt - t * speed
// 初始赛道状态
const checkRaceInitState = () =>
`${rabbit}${turtle}${start}${pad.repeat(steps)}${end}`
// 兔子领先时的赛道状态
const checkRaceState = () =>
`${start}${pad.repeat(t * speed)}${turtle}${pad.repeat(t * speed * 3)}${rabbit}${pad.repeat(getRabbitLastSteps())}${end}`
// 兔子停下后的赛道状态
const checkBackRaceState = () => {
if (getGapSteps() <= 0) {
if (getTurtleLastSteps() === 0) {
return `${start}${pad.repeat(stopAt)}${rabbit}${pad.repeat(steps - stopAt)}${end}${turtle}`
}
return `${start}${pad.repeat(stopAt)}${rabbit}${pad.repeat(t * speed - stopAt)}${turtle}${pad.repeat(getTurtleLastSteps())}${end}`
}
return `${start}${pad.repeat(t * speed)}${turtle}${pad.repeat(getGapSteps())}${rabbit}${pad.repeat(steps - stopAt)}${end}`
}
// 将定时器包装为 Promise
const wait = (sec) => new Promise(resolve => setTimeout(() => resolve(), sec))
// 使用 chalk-animation 模块实现终端特效刷新
const chalkWorker = require('chalk-animation')
const initState = checkRaceInitState()
const racing = chalkWorker.rainbow(initState)
const updateRaceTrack = (state) => {
racing.replace(state)
}
const race = () => {
let timer = setInterval(() => {
if (!stoped) {
if (getRabbitLastSteps() <= (steps - stopAt)) {
stoped = true
}
}
if (stoped) {
updateRaceTrack(checkBackRaceState())
if (getTurtleLastSteps() === 0) {
clearInterval(timer)
return
}
} else {
updateRaceTrack(checkRaceState())
}
t++
}, 150)
}
// 等待 2 秒后开始比赛
wait(2000).then(() => race())此实现中使用了箭头函数、Promise、const/let、模板字符串等 ES6 特性,使代码结构较为简洁,Promise 的引入也避免了过深的回调嵌套。
Class 重构实现
利用 ES6 Class、解构赋值、rest 参数、Proxy 等特性,将上述函数式实现重构为面向对象风格:
const chalkWorker = require('chalk-animation')
class Race extends Object {
constructor(props = {}) {
super(props)
;[
['rabbit', '兔子'],
['turtle', '乌龟'],
['turtleStep', 0],
['rabbitStep', 0],
['start', '|'],
['end', '》'],
['pad', '.'],
['speed', 1],
['steps', 50],
['stopAt', 42]
].forEach(elem => {
const [key, value] = elem
if (!(key in props)) {
this[key] = value
}
})
}
getRaceTrack () {
const { start, pad, turtle, turtleStep, rabbit, rabbitStep, steps, end } = this
if (!turtleStep && !rabbitStep) {
return `${turtle}${rabbit}${start}${pad.repeat(steps)}${end}`
}
const [[minStr, min], [maxStr, max]] = [
[turtle, turtleStep],
[rabbit, rabbitStep]
].sort((a, b) => a[1] - b[1])
const prefix = `${pad.repeat((min || 1) - 1)}`
const middle = `${pad.repeat(max - min)}`
const suffix = `${pad.repeat(steps - max)}`
const _start = `${start}${prefix}${minStr}`
const _end = suffix ? `${maxStr}${suffix}${end}` : `${end}${maxStr}`
return `${_start}${middle}${_end}`
}
updateRaceTrack (state, racing) {
racing.replace(state)
}
updateSteps () {
if (this.turtleStep >= this.steps) return
if (this.rabbitStep <= this.stopAt) {
this.rabbitStep += 3 * this.speed
}
this.turtleStep += 1 * this.speed
}
race () {
const initState = this.getRaceTrack()
const racing = chalkWorker.rainbow(initState)
let t = 0
let timer = setInterval(() => {
if (t <= 6) { t += 1; return }
this.updateRaceTrack(this.getRaceTrack(), racing)
this.updateSteps()
}, 150)
}
}
// 使用 Proxy 实现无 new 调用
const proxy = new Proxy(Race, {
apply (target, ctx, args) {
const race = new target(...args)
return race.race()
}
})
proxy()两种实现的对比启示
| 对比维度 | 函数式实现 | Class 重构实现 |
|---|---|---|
| 语法特性 | const/let、箭头函数、模板字符串、Promise | Class、解构赋值、rest 参数、Proxy、默认参数 |
| 状态管理 | 全局变量散落 | 封装在实例属性中 |
| 扩展性 | 较弱,增加功能需修改多处 | 较好,可通过继承扩展 |
| 复杂度 | 简单直接 | 引入额外抽象层,存在过度设计风险 |
两种实现方式的核心差异在于:ES6+ 的语法特性不仅改变了代码的书写形式,更影响了程序的架构组织方式。选择何种方式应取决于项目规模和实际需求,而非盲目追求新语法。
思考练习: 若要让兔子停留的位置随机化,应如何修改上述代码?
最佳实践
代码组织
// 推荐的项目结构
project/
├── src/
│ ├── config/ # 配置文件
│ ├── utils/ # 工具函数
│ ├── modules/ # 功能模块
│ └── index.js # 入口文件
├── tests/ # 测试文件
├── logs/ # 日志文件
├── .env # 环境变量
├── .env.example # 环境变量示例
├── package.json
└── README.md错误处理
// 使用错误优先回调
fs.readFile("file.txt", "utf8", (err, data) => {
if (err) {
console.error(err)
return
}
console.log(data)
})
// 使用 try-catch
async function readFile(file) {
try {
const data = await fs.promises.readFile(file, "utf8")
return data
} catch (err) {
console.error(`读取文件 ${file} 失败:`, err.message)
throw err
}
}
// 自定义错误
class FileNotFoundError extends Error {
constructor(filePath) {
super(`文件不存在: ${filePath}`)
this.name = "FileNotFoundError"
this.filePath = filePath
}
}
async function safeReadFile(filePath) {
try {
await fs.promises.access(filePath, fs.constants.F_OK)
} catch {
throw new FileNotFoundError(filePath)
}
return fs.promises.readFile(filePath, "utf8")
}资源清理
// 确保资源被正确释放
function processFile(filePath) {
let fd = null
try {
fd = fs.openSync(filePath, "r")
// 处理文件...
} finally {
if (fd !== null) {
fs.closeSync(fd)
}
}
}
// 使用 withFileTypes 选项简化操作
async function processDirectory(dir) {
const files = await fs.promises.readdir(dir, { withFileTypes: true })
for (const file of files) {
if (file.isFile()) {
// 处理文件
} else if (file.isDirectory()) {
// 处理目录
}
}
}更新记录:
- 新增文档目录和章节导航
- 补充 Buffer 类详细说明和使用示例
- 补充 global 和 globalThis 说明
- 补充 URL 和 URLSearchParams API
- 补充 TextEncoder 和 TextDecoder
- 新增定时器函数章节
- 完善 process 对象的表格和方法说明
- 补充 fs 模块的 Promise API
- 新增目录操作详细示例
- 新增命令行工具完整示例
- 新增常见问题解答章节
- 新增最佳实践章节
- 优化代码示例和注释
- 新增 ECMAScript 标准演进与 Node.js 支持章节
- 新增实战视角:Webpack 源码中的 ES6+ 语法特性章节
- 新增案例实战:命令行动画龟兔赛跑章节
Node.js 22+ 现代全局 API
Node.js 22+ 已原生支持多个 Web 标准 API,无需安装任何第三方库即可使用。
全局 fetch API
Node.js 22+ 内置了符合 Web 标准的 fetch API,替代了 node-fetch 等第三方库:
// 基本用法
const response = await fetch('https://api.example.com/users')
const users = await response.json()
// POST 请求
const res = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice', age: 30 })
})
// 处理错误
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
// 流式读取
const response = await fetch('https://example.com/large-file')
const reader = response.body.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) break
process.stdout.write(value)
}WebSocket 客户端
Node.js 22+ 内置了符合 Web 标准的 WebSocket API:
const ws = new WebSocket('ws://localhost:8080')
ws.addEventListener('open', () => {
console.log('连接已建立')
ws.send('Hello Server!')
})
ws.addEventListener('message', (event) => {
console.log('收到消息:', event.data)
})
ws.addEventListener('close', () => {
console.log('连接已关闭')
})
ws.addEventListener('error', (error) => {
console.error('WebSocket 错误:', error)
})structuredClone 深拷贝
Node.js 22+ 原生支持 structuredClone(),可进行深拷贝,支持循环引用和多种数据类型:
// 基本用法
const original = { name: 'Alice', hobbies: ['reading', 'coding'] }
const cloned = structuredClone(original)
cloned.hobbies.push('gaming')
console.log(original.hobbies) // ['reading', 'coding'] - 不受影响
// 支持循环引用
const obj = { name: 'self' }
obj.self = obj
const cloned = structuredClone(obj) // 不会报错
// 支持的类型
structuredClone(new Date()) // Date
structuredClone(new RegExp('\\d+')) // RegExp
structuredClone(new Map([['a', 1]])) // Map
structuredClone(new Set([1, 2, 3])) // Set
structuredClone(new ArrayBuffer(8)) // ArrayBuffer
structuredClone(new Int32Array(4)) // TypedArray
structuredClone(new Blob(['text'])) // Blob
// 不支持的类型会抛出 DataCloneError
// structuredClone(function() {}) // Function - 不支持
// structuredClone(new Error()) // Error - 不支持globalThis 统一全局对象
globalThis 是 ES2020 标准引入的全局对象引用,在所有 JavaScript 环境中统一:
// Node.js 中
console.log(globalThis === global) // true
// 浏览器中
// console.log(globalThis === window) // true
// Web Worker 中
// console.log(globalThis === self) // true
// 推荐使用 globalThis 而非 global,代码可跨环境复用
globalThis.myApp = { version: '1.0.0' }Web Streams API
Node.js 22+ 原生支持 Web Streams API,与浏览器 API 完全一致:
// ReadableStream
const stream = new ReadableStream({
start(controller) {
controller.enqueue('Hello')
controller.enqueue('World')
controller.close()
}
})
const reader = stream.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) break
console.log(value)
}
// TransformStream
const transform = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toString().toUpperCase())
}
})
// Node.js Stream 与 Web Stream 互转
import { Readable } from 'node:stream'
const nodeStream = Readable.toWeb(fs.createReadStream('file.txt'))Web Crypto API
Node.js 22+ 支持 Web Crypto API 子集:
const crypto = globalThis.crypto
// 生成随机值
const array = new Uint32Array(1)
crypto.getRandomValues(array)
// 生成 UUID
console.log(crypto.randomUUID()) // '550e8400-e29b-41d4-a716-446655440000'
// 摘要计算
const encoder = new TextEncoder()
const data = encoder.encode('Hello World')
const hash = await crypto.subtle.digest('SHA-256', data)
console.log(Buffer.from(hash).toString('hex'))现代全局 API 速查表
| API | 引入版本 | 说明 | 替代方案 |
|---|---|---|---|
fetch | v18 实验性 / v22 稳定 | HTTP 请求 | node-fetch、axios |
WebSocket | v22 实验性 / v23 稳定 | WebSocket 客户端 | ws |
structuredClone | v17 稳定 | 深拷贝 | lodash.cloneDeep |
globalThis | v12 稳定 | 统一全局对象 | global、window |
ReadableStream | v18 实验性 / v22 稳定 | Web 可读流 | node:stream |
crypto.subtle | v15 实验性 / v22 稳定 | Web Crypto | node:crypto |
URL / URLSearchParams | v10 稳定 | URL 解析 | url 模块 |
TextEncoder / TextDecoder | v11 稳定 | 文本编解码 | iconv-lite |
BroadcastChannel | v15 实验性 / v22 稳定 | 跨线程通信 | worker_threads |