{T}

基础语法

本文档详细介绍 Node.js 的核心语法特性,包括全局对象、模块系统、文件操作等基础知识。


ECMAScript 标准演进与 Node.js 支持

ECMAScript 标准简史

JavaScript 最初由网景浏览器于 1995 年发布,名为 LiveScript,后更名为 JavaScript。随后提交至 ECMA(欧洲计算机制造商协会)进行标准化,官方学名为 ECMAScript。由于 ECMAScript 名称不够通俗,开发者仍习惯使用 JavaScript。

ES 标准的演进并非一帆风顺。标准协会负责制定规范,而实际落地执行的是浏览器厂商,各方对标准的认同度不一,导致标准推进缓慢。1999 年 ES3 至 2009 年 ES5 的十年间,语言标准几乎停滞不前。期间曾推出 ES4,但因改动过于激进而废弃,未在厂商侧落地。直到 2009 年,语言标准才重新步入正轨。

版本命名说明:

术语说明
JSJavaScript 的简称,代指实现了 ECMAScript 标准的任何语言版本
ESECMAScript 的简称
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 提供一些全局可用的对象和变量,无需引入即可直接使用。

全局对象概览

code
┌─────────────────────────────────────────────────────────────┐
│                    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()结束分组-

基本输出

javascript
// 普通输出
console.log("Hello Node.js")

// 错误输出(输出到 stderr)
console.error("这是一个错误信息")

// 警告输出
console.warn("这是一个警告信息")

// 信息输出
console.info("这是一条信息")

// 调试输出(仅在调试模式下显示)
console.debug("调试信息")

// 清空控制台
console.clear()

对象输出

javascript
// 输出对象(格式化显示)
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

表格输出

javascript
// 表格形式输出数组
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 列
)

计时功能

javascript
// 基本计时
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")

计数器

javascript
// 计数器示例
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("处理项目")

分组输出

javascript
// 分组输出
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()

堆栈跟踪

javascript
// 堆栈跟踪
function funcA() {
  funcB()
}

function funcB() {
  funcC()
}

function funcC() {
  console.trace("调用堆栈信息")
}

funcA()

断言

javascript
// 断言(条件为 false 时输出错误)
console.assert(1 === 2, "1 不等于 2") // 会输出错误信息
console.assert(true, "这不会输出")     // 不会输出

// 对象断言
const user = { name: "张三", age: 25 }
console.assert(user.age >= 18, "用户未成年")

格式化输出

javascript
// 使用占位符
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)

推荐使用模板字符串

javascript
// 使用模板字符串(推荐)
const name = "张三"
const age = 25
console.log(`姓名: ${name}, 年龄: ${age}`)

重定向输出

javascript
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运行程序所在的平台系统darwinwin32linux
arch当前 CPU 的架构x64arm64
pid当前进程的进程号12345
ppid当前进程的父进程的进程号12344
title进程名(可修改)node
argv命令行参数数组['node', 'app.js', 'arg1']
execArgvNode 可执行文件与脚本文件之间的命令行参数['--require', 'dotenv']
execPath执行当前脚本的 Node 二进制文件的绝对路径/usr/local/bin/node
env当前系统的环境变量{ PATH: '...', HOME: '...' }
cwd()当前工作目录/Users/project
config编译当前 Node 执行文件的配置选项对象{ ... }
connected如果进程是通过 IPC 通道派生的,返回 truetrue/false

内存和资源属性

属性/方法说明
memoryUsage()返回 Node 进程内存使用状况(字节)
cpuUsage()返回 CPU 使用情况
resourceUsage()返回资源使用情况
uptime()返回 Node 已经运行的秒数
hrtime()高精度时间(纳秒级)

进程信息示例

javascript
// 进程基本信息
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',
//   ...
// }

环境变量

javascript
// 所有环境变量
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"
// }

命令行参数

javascript
// 命令行参数数组
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' }

使用第三方库解析参数

javascript
// 推荐使用 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
javascript
// 退出进程
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())

内存使用监控

javascript
// 获取内存使用情况
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 使用监控

javascript
// 获取 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`)

高精度时间

javascript
// 高精度时间(纳秒级)
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]}纳秒`)

事件监听

javascript
// 监听未捕获的异常
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)

javascript
// 检查是否有 IPC 通道
if (process.connected) {
  // 发送消息给父进程
  process.send({ type: "ready", data: "子进程已就绪" })

  // 接收父进程消息
  process.on("message", (message) => {
    console.log("收到父进程消息:", message)
  })
}

// 断开 IPC 通道
process.disconnect()

完整示例:命令行程序

javascript
// 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)
}

运行示例:

bash
node app.js info
node app.js memory
node app.js env NODE_ENV
node app.js cwd

Buffer 类

Buffer 是 Node.js 中处理二进制数据的核心类,用于在 TCP 流、文件系统操作等场景中处理原始数据。

Buffer 特点

  • Buffer 类似于整数数组,但对应 V8 堆内存之外的一块原始内存
  • Buffer 大小在创建时确定,无法更改
  • Buffer 用于处理二进制数据流
  • Buffer 在 Node.js 中全局可用,无需 require

创建 Buffer

javascript
// 方式一:分配指定大小的 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 支持的字符编码:

编码说明
utf8UTF-8(默认)
asciiASCII
utf16leUTF-16 Little Endian
ucs2UTF-16 LE(utf16le 别名)
base64Base64 编码
base64urlBase64 URL 安全编码
latin1Latin-1 (ISO-8859-1)
binary二进制(latin1 别名)
hex十六进制
javascript
// 不同编码的 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

javascript
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 方法

javascript
// 拼接 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

javascript
// 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 实用示例

javascript
// 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

javascript
// 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 对象

javascript
// 创建 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

javascript
// 创建 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 解析工具

javascript
// 解析请求 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。

javascript
// 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

javascript
// 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

javascript
// 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, setTimeout

process.nextTick

javascript
// process.nextTick - 当前操作完成后立即执行(微任务)
process.nextTick(() => {
  console.log("nextTick 执行")
})

console.log("同步代码")

// 输出顺序:
// 1. 同步代码
// 2. nextTick 执行

执行顺序

javascript
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)

执行优先级

code
同步代码 > process.nextTick > Promise > setTimeout/setImmediate

定时器最佳实践

javascript
// ❌ 避免:长时间运行的定时器
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

这两个全局变量提供了当前模块的目录和文件路径信息。

基本用法

javascript
// __dirname - 当前模块所在的目录路径(绝对路径)
console.log(__dirname)
// 例如: /Users/username/project/src

// __filename - 当前模块文件的绝对路径
console.log(__filename)
// 例如: /Users/username/project/src/app.js

路径操作

javascript
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'
// }

读取文件

javascript
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

javascript
// __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 中的替代方案

javascript
// 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 是引入模块的核心函数。

模块系统架构

code
┌─────────────────────────────────────────────────────────────┐
│                    CommonJS 模块系统                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   导出方式:                                                 │
│   ┌─────────────────────────────────────────────────────┐  │
│   │ exports.xxx = value     # 导出多个属性              │  │
│   │ module.exports = value  # 导出单个值或对象          │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   导入方式:                                                 │
│   ┌─────────────────────────────────────────────────────┐  │
│   │ const module = require('module-name')               │  │
│   │ const { xxx, yyy } = require('./local-module')      │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

require 函数

基本用法

javascript
// 引入内置模块
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")

模块查找顺序

code
┌─────────────────────────────────────────────────────────────┐
│                    模块查找顺序                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 内置模块(如 fs、http、path)                           │
│     ↓                                                       │
│  2. 相对路径(./ 或 ../)                                   │
│     ↓                                                       │
│  3. 绝对路径(/)                                           │
│     ↓                                                       │
│  4. node_modules 目录                                       │
│     ↓                                                       │
│  5. 向上查找 node_modules(直到根目录)                     │
│     ↓                                                       │
│  6. 全局 node_modules                                       │
│     ↓                                                       │
│  7. NODE_PATH 环境变量指定的路径                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
javascript
// 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
// ... 直到根目录

模块缓存

javascript
// 模块只会被加载一次,后续 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

javascript
// 根据条件动态加载模块
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模块所在目录
javascript
// 当前模块信息
console.log("模块 ID:", module.id)
console.log("文件路径:", module.filename)
console.log("已加载:", module.loaded)
console.log("查找路径:", module.paths)

// 子模块
console.log("子模块:", module.children)

module.exports 和 exports

导出单个值

javascript
// math.js - 导出函数
function add(a, b) {
  return a + b
}

module.exports = add

// app.js
const add = require("./math")
console.log(add(1, 2)) // 3

导出对象

javascript
// 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

javascript
// 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))

导出类

javascript
// 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

javascript
// 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 模块

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 模块

javascript
// 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 原生模块

javascript
// 编译后的 C++ 插件(.node 文件)
const addon = require("./build/Release/addon.node")

// 通常用于性能敏感的操作
// 如:图像处理、加密算法等

目录作为模块

javascript
// 目录结构
// 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

javascript
// 方式一:使用 .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 语法

javascript
// 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 互操作

javascript
// 在 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 关键字的函数定义,词法绑定 thisconst 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 模块概述

javascript
const fs = require("fs")

// fs 模块提供三种 API 风格:
// 1. 异步回调 API:fs.readFile(path, callback)
// 2. 同步 API:fs.readFileSync(path)
// 3. Promise API:fs.promises.readFile(path)

异步文件操作

读取文件

javascript
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)
})

写入文件

javascript
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("写入完成")
})

检查文件状态

javascript
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))
})

权限常量

javascript
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 事件循环,通常不推荐使用,但在某些场景下(如启动脚本)可能有用。

javascript
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。

javascript
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)
  }
}

目录操作

创建目录

javascript
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)
  }
}

读取目录

javascript
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)
  }
}

删除目录

javascript
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)
  }
}

文件操作完整示例

文件复制工具

javascript
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("复制成功")
})

目录遍历

javascript
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)
})

查找文件

javascript
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)

命令行程序示例

javascript
// 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()

使用示例:

bash
# 显示帮助
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 有什么区别?

javascript
// __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: 如何安全地使用环境变量?

javascript
// ❌ 不安全:直接使用可能不存在的环境变量
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 convict

Q: 如何正确处理未捕获的异常?

javascript
// 全局异常处理
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 有什么区别?

javascript
// 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
// - 需要兼容旧代码时使用 CommonJS

Q: 如何实现模块热重载?

javascript
// 开发环境热重载
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: 如何处理大文件?

javascript
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: 如何处理文件编码问题?

javascript
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: 如何递归创建目录?

javascript
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: 如何提高文件操作性能?

javascript
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 米处停下休息
  • 乌龟匀速前进,最终越过终点获胜

赛道状态演变:

code
起始状态:    兔子乌龟|..............................》
兔子领先:    |......乌龟...................兔子.....》
乌龟追上:    |.........................兔子乌龟.....》
乌龟获胜:    |.........................兔子.....》乌龟

函数式实现

核心思路:每隔固定时间间隔,根据龟兔当前距离计算赛道字符串,刷新终端输出。

javascript
// 声明比赛队员与赛道参数
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 等特性,将上述函数式实现重构为面向对象风格:

javascript
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、箭头函数、模板字符串、PromiseClass、解构赋值、rest 参数、Proxy、默认参数
状态管理全局变量散落封装在实例属性中
扩展性较弱,增加功能需修改多处较好,可通过继承扩展
复杂度简单直接引入额外抽象层,存在过度设计风险

两种实现方式的核心差异在于:ES6+ 的语法特性不仅改变了代码的书写形式,更影响了程序的架构组织方式。选择何种方式应取决于项目规模和实际需求,而非盲目追求新语法。

思考练习: 若要让兔子停留的位置随机化,应如何修改上述代码?


最佳实践

代码组织

javascript
// 推荐的项目结构
project/
├── src/
│   ├── config/        # 配置文件
│   ├── utils/         # 工具函数
│   ├── modules/       # 功能模块
│   └── index.js       # 入口文件
├── tests/             # 测试文件
├── logs/              # 日志文件
├── .env               # 环境变量
├── .env.example       # 环境变量示例
├── package.json
└── README.md

错误处理

javascript
// 使用错误优先回调
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")
}

资源清理

javascript
// 确保资源被正确释放
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 等第三方库:

javascript
// 基本用法
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:

javascript
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(),可进行深拷贝,支持循环引用和多种数据类型:

javascript
// 基本用法
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 环境中统一:

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 完全一致:

javascript
// 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 子集:

javascript
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引入版本说明替代方案
fetchv18 实验性 / v22 稳定HTTP 请求node-fetchaxios
WebSocketv22 实验性 / v23 稳定WebSocket 客户端ws
structuredClonev17 稳定深拷贝lodash.cloneDeep
globalThisv12 稳定统一全局对象globalwindow
ReadableStreamv18 实验性 / v22 稳定Web 可读流node:stream
crypto.subtlev15 实验性 / v22 稳定Web Cryptonode:crypto
URL / URLSearchParamsv10 稳定URL 解析url 模块
TextEncoder / TextDecoderv11 稳定文本编解码iconv-lite
BroadcastChannelv15 实验性 / v22 稳定跨线程通信worker_threads