Day07_Node.js 深度解析:从模块系统到文件操作全指南

一篇面向实战的 Node 进阶博客:内置模块(path / fs / url / querystring)异常与 JSONCommonJS 与 ESM 的完整链路。示例可独立运行,不依赖外部讲义路径。

目录

  • 导读:知识架构与权威参考
  • [1. Node.js 模块系统概述](#1. Node.js 模块系统概述)
  • [2. 内置模块详解](#2. 内置模块详解)
    • [2.1 Path 路径操作模块](#2.1 Path 路径操作模块)
    • [2.2 FS 文件系统模块](#2.2 FS 文件系统模块)
    • [2.3 URL 模块](#2.3 URL 模块)
    • [2.4 QueryString 模块](#2.4 QueryString 模块)
  • [3. 异常处理机制](#3. 异常处理机制)
    • [3.1 错误对象基础](#3.1 错误对象基础)
    • [3.2 throw 抛出错误](#3.2 throw 抛出错误)
    • [3.3 try-catch-finally 结构](#3.3 try-catch-finally 结构)
  • [4. JSON 数据处理](#4. JSON 数据处理)
    • [4.1 JSON 格式详解](#4.1 JSON 格式详解)
    • [4.2 JSON 对象方法](#4.2 JSON 对象方法)
    • [4.3 JSON 实战应用](#4.3 JSON 实战应用)
  • [5. 模块化规范深度解析](#5. 模块化规范深度解析)
    • [5.0 主流模块化规范对照](#5.0 主流模块化规范对照)
    • [5.1 CommonJS 规范](#5.1 CommonJS 规范)
    • [5.2 ES6 模块规范](#5.2 ES6 模块规范)
  • [6. 实战应用场景](#6. 实战应用场景)
    • [6.1 文件服务器实现](#6.1 文件服务器实现)
    • [6.2 日志系统实现](#6.2 日志系统实现)
    • [6.3 配置管理系统](#6.3 配置管理系统)
  • [7. 核心案例速查与知识点归纳](#7. 核心案例速查与知识点归纳)
    • [7.1 案例学习路线](#7.1 案例学习路线)
    • [7.2 fs API 速查](#7.2 fs API 速查)
    • [7.3 CommonJS 暴露/import 速查](#7.3 CommonJS 暴露/import 速查)
    • [7.4 ESM 启用方式](#7.4 ESM 启用方式)
    • [7.5 实战:容量单位转换模块(CJS + ESM)](#7.5 实战:容量单位转换模块(CJS + ESM))
    • [7.6 可运行 HTML:JSON 与 querystring 对照](#7.6 可运行 HTML:JSON 与 querystring 对照)
    • [7.7 常见错误码](#7.7 常见错误码)
  • 总结

导读:知识架构与权威参考

本文解决什么问题

板块 核心能力 典型场景
path / fs 路径拼接、读写、目录、流式复制 日志、配置、静态资源、大文件
url / querystring 解析 URL、序列化查询串 路由参数、表单、爬虫
异常处理 Errorthrowtry/catch 稳健 CLI、服务启动
JSON stringify / parse 配置、API 数据交换
模块化 CJS require、ESM import 工具库拆分、工程化

知识脉络(Mermaid)

#mermaid-svg-fQk8mOPPvinHPVGW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fQk8mOPPvinHPVGW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fQk8mOPPvinHPVGW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fQk8mOPPvinHPVGW .error-icon{fill:#552222;}#mermaid-svg-fQk8mOPPvinHPVGW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fQk8mOPPvinHPVGW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fQk8mOPPvinHPVGW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fQk8mOPPvinHPVGW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fQk8mOPPvinHPVGW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fQk8mOPPvinHPVGW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fQk8mOPPvinHPVGW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fQk8mOPPvinHPVGW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fQk8mOPPvinHPVGW .marker.cross{stroke:#333333;}#mermaid-svg-fQk8mOPPvinHPVGW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fQk8mOPPvinHPVGW p{margin:0;}#mermaid-svg-fQk8mOPPvinHPVGW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fQk8mOPPvinHPVGW .cluster-label text{fill:#333;}#mermaid-svg-fQk8mOPPvinHPVGW .cluster-label span{color:#333;}#mermaid-svg-fQk8mOPPvinHPVGW .cluster-label span p{background-color:transparent;}#mermaid-svg-fQk8mOPPvinHPVGW .label text,#mermaid-svg-fQk8mOPPvinHPVGW span{fill:#333;color:#333;}#mermaid-svg-fQk8mOPPvinHPVGW .node rect,#mermaid-svg-fQk8mOPPvinHPVGW .node circle,#mermaid-svg-fQk8mOPPvinHPVGW .node ellipse,#mermaid-svg-fQk8mOPPvinHPVGW .node polygon,#mermaid-svg-fQk8mOPPvinHPVGW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fQk8mOPPvinHPVGW .rough-node .label text,#mermaid-svg-fQk8mOPPvinHPVGW .node .label text,#mermaid-svg-fQk8mOPPvinHPVGW .image-shape .label,#mermaid-svg-fQk8mOPPvinHPVGW .icon-shape .label{text-anchor:middle;}#mermaid-svg-fQk8mOPPvinHPVGW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fQk8mOPPvinHPVGW .rough-node .label,#mermaid-svg-fQk8mOPPvinHPVGW .node .label,#mermaid-svg-fQk8mOPPvinHPVGW .image-shape .label,#mermaid-svg-fQk8mOPPvinHPVGW .icon-shape .label{text-align:center;}#mermaid-svg-fQk8mOPPvinHPVGW .node.clickable{cursor:pointer;}#mermaid-svg-fQk8mOPPvinHPVGW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fQk8mOPPvinHPVGW .arrowheadPath{fill:#333333;}#mermaid-svg-fQk8mOPPvinHPVGW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fQk8mOPPvinHPVGW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fQk8mOPPvinHPVGW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fQk8mOPPvinHPVGW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fQk8mOPPvinHPVGW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fQk8mOPPvinHPVGW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fQk8mOPPvinHPVGW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fQk8mOPPvinHPVGW .cluster text{fill:#333;}#mermaid-svg-fQk8mOPPvinHPVGW .cluster span{color:#333;}#mermaid-svg-fQk8mOPPvinHPVGW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fQk8mOPPvinHPVGW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fQk8mOPPvinHPVGW rect.text{fill:none;stroke-width:0;}#mermaid-svg-fQk8mOPPvinHPVGW .icon-shape,#mermaid-svg-fQk8mOPPvinHPVGW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fQk8mOPPvinHPVGW .icon-shape p,#mermaid-svg-fQk8mOPPvinHPVGW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fQk8mOPPvinHPVGW .icon-shape .label rect,#mermaid-svg-fQk8mOPPvinHPVGW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fQk8mOPPvinHPVGW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fQk8mOPPvinHPVGW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fQk8mOPPvinHPVGW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 模块化
语言机制
内置模块
path
fs 含 Stream
url
querystring
异常处理
JSON
CommonJS
ES Modules

权威文档

主题 链接
Node.js API 总览 nodejs.org/api
fs nodejs.org/api/fs.html
path nodejs.org/api/path.html
url nodejs.org/api/url.html
Modules: CJS nodejs.org/api/modules.html
Modules: ESM nodejs.org/api/esm.html
JSON MDN --- JSON

与工程化的关系

  • Webpack / Vite :用 path.resolve 定位入口,fs 读源码写产物。
  • Express 雏形 :静态文件服务即本章 fs + path + Content-Type
  • 前后端分离 :接口返回 JSON 字符串,前端 fetch + JSON.parse

1. Node.js 模块系统概述

Node.js 中的模块分为三种类型:
#mermaid-svg-7Q6FSurZC91y5a1H{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7Q6FSurZC91y5a1H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7Q6FSurZC91y5a1H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7Q6FSurZC91y5a1H .error-icon{fill:#552222;}#mermaid-svg-7Q6FSurZC91y5a1H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7Q6FSurZC91y5a1H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7Q6FSurZC91y5a1H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7Q6FSurZC91y5a1H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7Q6FSurZC91y5a1H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7Q6FSurZC91y5a1H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7Q6FSurZC91y5a1H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7Q6FSurZC91y5a1H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7Q6FSurZC91y5a1H .marker.cross{stroke:#333333;}#mermaid-svg-7Q6FSurZC91y5a1H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7Q6FSurZC91y5a1H p{margin:0;}#mermaid-svg-7Q6FSurZC91y5a1H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7Q6FSurZC91y5a1H .cluster-label text{fill:#333;}#mermaid-svg-7Q6FSurZC91y5a1H .cluster-label span{color:#333;}#mermaid-svg-7Q6FSurZC91y5a1H .cluster-label span p{background-color:transparent;}#mermaid-svg-7Q6FSurZC91y5a1H .label text,#mermaid-svg-7Q6FSurZC91y5a1H span{fill:#333;color:#333;}#mermaid-svg-7Q6FSurZC91y5a1H .node rect,#mermaid-svg-7Q6FSurZC91y5a1H .node circle,#mermaid-svg-7Q6FSurZC91y5a1H .node ellipse,#mermaid-svg-7Q6FSurZC91y5a1H .node polygon,#mermaid-svg-7Q6FSurZC91y5a1H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7Q6FSurZC91y5a1H .rough-node .label text,#mermaid-svg-7Q6FSurZC91y5a1H .node .label text,#mermaid-svg-7Q6FSurZC91y5a1H .image-shape .label,#mermaid-svg-7Q6FSurZC91y5a1H .icon-shape .label{text-anchor:middle;}#mermaid-svg-7Q6FSurZC91y5a1H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7Q6FSurZC91y5a1H .rough-node .label,#mermaid-svg-7Q6FSurZC91y5a1H .node .label,#mermaid-svg-7Q6FSurZC91y5a1H .image-shape .label,#mermaid-svg-7Q6FSurZC91y5a1H .icon-shape .label{text-align:center;}#mermaid-svg-7Q6FSurZC91y5a1H .node.clickable{cursor:pointer;}#mermaid-svg-7Q6FSurZC91y5a1H .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7Q6FSurZC91y5a1H .arrowheadPath{fill:#333333;}#mermaid-svg-7Q6FSurZC91y5a1H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7Q6FSurZC91y5a1H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7Q6FSurZC91y5a1H .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7Q6FSurZC91y5a1H .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7Q6FSurZC91y5a1H .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7Q6FSurZC91y5a1H .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7Q6FSurZC91y5a1H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7Q6FSurZC91y5a1H .cluster text{fill:#333;}#mermaid-svg-7Q6FSurZC91y5a1H .cluster span{color:#333;}#mermaid-svg-7Q6FSurZC91y5a1H div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7Q6FSurZC91y5a1H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7Q6FSurZC91y5a1H rect.text{fill:none;stroke-width:0;}#mermaid-svg-7Q6FSurZC91y5a1H .icon-shape,#mermaid-svg-7Q6FSurZC91y5a1H .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7Q6FSurZC91y5a1H .icon-shape p,#mermaid-svg-7Q6FSurZC91y5a1H .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7Q6FSurZC91y5a1H .icon-shape .label rect,#mermaid-svg-7Q6FSurZC91y5a1H .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7Q6FSurZC91y5a1H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7Q6FSurZC91y5a1H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7Q6FSurZC91y5a1H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Node.js 模块系统
内置模块
自定义模块
第三方模块
fs path http
用户创建的.js文件
npm express lodash

模块系统核心概念

模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。

模块化特点:

  • 所有代码都运行在模块作用域,不会污染全局作用域
  • 模块可以多次加载,但只会在第一次加载时运行一次,然后运行结果就被缓存了
  • 模块加载顺序按照代码书写顺序执行

模块化带来的好处:

  • 提高代码的复用性
  • 提高代码的可维护性
  • 实现按需加载

【代码注释】

  • 内置模块fspath):require('fs') 即可,无需 npm 安装。
  • 自定义模块 :项目内 .js 文件,通过 module.exports 暴露、require('./x') 引入。
  • 第三方模块npm install 后从 node_modules 加载,如 express
  • 每个文件是一个独立模块作用域;顶层 var 不会挂到 global,避免污染。
  • 同一文件多次 require 只执行一次顶层代码(缓存),与 §7.3 速查一致。

2. 内置模块详解

2.1 Path 路径操作模块

Path 模块提供了处理文件路径和目录路径的实用工具,是跨平台路径操作的核心模块。

核心方法详解
1. path.join(path1, path2, ...)

功能描述:使用系统特定的分隔符将路径片段连接在一起,规范化生成的路径。

名词解释

  • 路径分隔符:Unix系统使用"/",Windows系统使用""
  • 路径规范化:将不同格式的路径转换为系统标准格式

实际应用示例

javascript 复制代码
const path = require('path');

const fullPath = path.join('user', 'documents', 'file.txt');
console.log(fullPath);

const mixedPath = path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
console.log(mixedPath);

const resolvedPath = path.join('a', 'b', '..', 'c');
console.log(resolvedPath);

【代码注释】

  • path.join 用当前系统的分隔符拼接片段(Windows 为 \,Unix 为 /),并去掉多余斜杠;不保证结果是绝对路径。
  • join('/foo', 'bar', ..., 'quux', '..').. 会吃掉 quux,得到 /foo/bar/baz/asdf;若片段里已含 /(如 baz/asdf),join 仍会正确拼接。
  • join('a', 'b', '..', 'c')a/c,适合在 __dirname 后接 datalogs 等相对片段。
  • 不要用字符串 __dirname + '/data/a.txt',跨平台与规范化都交给 path 模块。

经典使用场景

  • 构建配置文件路径(Webpack、Vite配置)
  • 日志文件路径管理
  • 静态资源路径处理

业界应用案例

  • Webpack:在模块解析时使用path.join构建绝对路径
  • VSCode:插件系统路径拼接
  • Create React App:构建工具中的路径处理
2. path.resolve(...paths)

功能描述:将路径或路径片段序列解析为绝对路径。

核心原理
#mermaid-svg-Rp8J3FjsWnwnRmel{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Rp8J3FjsWnwnRmel .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Rp8J3FjsWnwnRmel .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Rp8J3FjsWnwnRmel .error-icon{fill:#552222;}#mermaid-svg-Rp8J3FjsWnwnRmel .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Rp8J3FjsWnwnRmel .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Rp8J3FjsWnwnRmel .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Rp8J3FjsWnwnRmel .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Rp8J3FjsWnwnRmel .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Rp8J3FjsWnwnRmel .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Rp8J3FjsWnwnRmel .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Rp8J3FjsWnwnRmel .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Rp8J3FjsWnwnRmel .marker.cross{stroke:#333333;}#mermaid-svg-Rp8J3FjsWnwnRmel svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Rp8J3FjsWnwnRmel p{margin:0;}#mermaid-svg-Rp8J3FjsWnwnRmel .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Rp8J3FjsWnwnRmel .cluster-label text{fill:#333;}#mermaid-svg-Rp8J3FjsWnwnRmel .cluster-label span{color:#333;}#mermaid-svg-Rp8J3FjsWnwnRmel .cluster-label span p{background-color:transparent;}#mermaid-svg-Rp8J3FjsWnwnRmel .label text,#mermaid-svg-Rp8J3FjsWnwnRmel span{fill:#333;color:#333;}#mermaid-svg-Rp8J3FjsWnwnRmel .node rect,#mermaid-svg-Rp8J3FjsWnwnRmel .node circle,#mermaid-svg-Rp8J3FjsWnwnRmel .node ellipse,#mermaid-svg-Rp8J3FjsWnwnRmel .node polygon,#mermaid-svg-Rp8J3FjsWnwnRmel .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Rp8J3FjsWnwnRmel .rough-node .label text,#mermaid-svg-Rp8J3FjsWnwnRmel .node .label text,#mermaid-svg-Rp8J3FjsWnwnRmel .image-shape .label,#mermaid-svg-Rp8J3FjsWnwnRmel .icon-shape .label{text-anchor:middle;}#mermaid-svg-Rp8J3FjsWnwnRmel .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Rp8J3FjsWnwnRmel .rough-node .label,#mermaid-svg-Rp8J3FjsWnwnRmel .node .label,#mermaid-svg-Rp8J3FjsWnwnRmel .image-shape .label,#mermaid-svg-Rp8J3FjsWnwnRmel .icon-shape .label{text-align:center;}#mermaid-svg-Rp8J3FjsWnwnRmel .node.clickable{cursor:pointer;}#mermaid-svg-Rp8J3FjsWnwnRmel .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Rp8J3FjsWnwnRmel .arrowheadPath{fill:#333333;}#mermaid-svg-Rp8J3FjsWnwnRmel .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Rp8J3FjsWnwnRmel .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Rp8J3FjsWnwnRmel .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Rp8J3FjsWnwnRmel .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Rp8J3FjsWnwnRmel .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Rp8J3FjsWnwnRmel .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Rp8J3FjsWnwnRmel .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Rp8J3FjsWnwnRmel .cluster text{fill:#333;}#mermaid-svg-Rp8J3FjsWnwnRmel .cluster span{color:#333;}#mermaid-svg-Rp8J3FjsWnwnRmel div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Rp8J3FjsWnwnRmel .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Rp8J3FjsWnwnRmel rect.text{fill:none;stroke-width:0;}#mermaid-svg-Rp8J3FjsWnwnRmel .icon-shape,#mermaid-svg-Rp8J3FjsWnwnRmel .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Rp8J3FjsWnwnRmel .icon-shape p,#mermaid-svg-Rp8J3FjsWnwnRmel .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Rp8J3FjsWnwnRmel .icon-shape .label rect,#mermaid-svg-Rp8J3FjsWnwnRmel .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Rp8J3FjsWnwnRmel .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Rp8J3FjsWnwnRmel .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Rp8J3FjsWnwnRmel :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入路径片段
从右向左处理
遇到绝对路径停止
拼接所有相对路径
生成最终绝对路径

实际应用示例

javascript 复制代码
const path = require('path');

const absolutePath = path.resolve('static', 'assets', 'image.png');
console.log(absolutePath);

const multiPath = path.resolve('/foo', 'bar', 'baz');
console.log(multiPath);

const currentFileDir = path.resolve(__dirname, 'data.json');
console.log(currentFileDir);

const parentPath = path.resolve('/a/b', '../c');
console.log(parentPath);

【代码注释】

  • resolve 从右向左处理:遇到绝对路径 片段(如 /foo)则丢弃其左侧所有相对片段,最终得到规范化的绝对路径。
  • resolve('static', 'assets', 'image.png') 基于当前 cwd (执行 node 时所在目录),与脚本文件位置无关------这是与 join(__dirname, ...) 的核心区别。
  • resolve(__dirname, 'data.json') 推荐用于「无论从哪里启动 node,都找到当前模块旁的 data.json」。
  • 记忆:join = 拼路径;resolve = 拼完后钉在系统地图上的绝对坐标。

经典使用场景

  • 配置文件路径解析
  • 文件上传目录处理
  • 日志文件路径构建
3. path.basename(path, ext)

功能描述:返回路径的最后一部分,通常用于获取文件名。

实际应用示例

javascript 复制代码
const path = require('path');

const fileName = path.basename('/path/to/example.txt');
const baseName = path.basename('/path/to/example.txt', '.txt');
const multiExt = path.basename('/path/to/file.tar.gz', '.gz');
const dirName = path.basename('/path/to/directory/');

【代码注释】

  • basename(path) 取路径最后一段;第二参数 ext 若传入且匹配末尾扩展名,则去掉该扩展(如 .txt → 得到 example)。
  • basename('file.tar.gz', '.gz') 只去掉 .gz,剩余 file.tar;双扩展名需业务层自行判断。
  • 路径以 / 结尾时,最后一段视为目录名 directory,常用于区分「目录路径」与「文件路径」。
4. path.dirname(path)

功能描述:返回路径中目录的部分。

实际应用示例

javascript 复制代码
const path = require('path');

const dirPath = path.dirname('/path/to/example.txt');
const nestedDir = path.dirname('/path/to/nested/directory/file.js');
const currentDir = path.dirname('/path/to/');

【代码注释】

  • dirname 返回路径中去掉文件名 后的目录部分;与 CommonJS 的 __dirname 常量概念一致(dirname(__filename) === __dirname)。
  • 嵌套越深,dirname 只剥最后一层,不会递归到根目录。
  • 尾部带分隔符的路径,dirname('/path/to/')/path,写日志或拼配置时常与 basename 配合使用。
5. path.extname(path)

功能描述 :返回路径中文件的后缀名,从最后一个 . 开始到字符串结束。

实际应用示例

javascript 复制代码
const path = require('path');

console.log(path.extname('index.html'));
console.log(path.extname('package.json'));
console.log(path.extname('README'));
console.log(path.extname('.gitignore'));

【代码注释】

  • 返回值包含点 ,如 '.html''.json',便于与白名单数组 includes 比较。
  • 只认最后一个 点:app.min.js'.js'.tar.gz 需自行解析。
  • README.gitignore 返回 '';静态服务里常据此设置 Content-Type.htmltext/html)。

经典使用场景

  • 文件类型验证
  • 静态资源处理(图片、CSS、JS)
  • 文件上传时的类型检查
6. path.isAbsolute(path)

功能描述:判断路径是否为绝对路径。

实际应用示例

javascript 复制代码
const path = require('path');

console.log(path.isAbsolute('/foo/bar'));
console.log(path.isAbsolute('foo/bar'));
console.log(path.isAbsolute('C:\\foo\\bar'));
console.log(path.isAbsolute('\\\\server\\share'));

【代码注释】

  • Unix/macOS:以 / 开头为绝对路径;foo/bar 为相对路径。
  • Windows:C:\... 盘符路径、\\server\share UNC 路径为绝对;构建工具在解析 entry 时先用 isAbsolute 再决定 join 还是直接使用。
Path 模块综合应用案例
javascript 复制代码
const path = require('path');

// 【实战案例】构建静态资源服务器路径处理
class StaticPathManager {
    constructor(rootDir) {
        this.rootDir = path.resolve(rootDir);
    }

    buildPath(relativePath) {
        const fullPath = path.join(this.rootDir, relativePath);
        const normalizedPath = path.normalize(fullPath);

        if (!normalizedPath.startsWith(this.rootDir)) {
            throw new Error('非法路径访问');
        }

        return normalizedPath;
    }

    getFileInfo(filePath) {
        return {
            name: path.basename(filePath),
            dirname: path.dirname(filePath),
            extname: path.extname(filePath),
            isAbsolute: path.isAbsolute(filePath)
        };
    }
}

const pathManager = new StaticPathManager('./public');
const imagePath = pathManager.buildPath('images/logo.png');
console.log('完整路径:', imagePath);
console.log('文件信息:', pathManager.getFileInfo(imagePath));

【代码注释】

  • 构造函数 path.resolve(rootDir)./public 钉成绝对根目录,后续所有请求都相对该根解析。
  • buildPathjoinnormalize(处理 ..、重复斜杠),然后用 startsWith(this.rootDir) 拦截 ../../../etc/passwd路径遍历攻击。
  • 注意:Windows 上应用 path.resolve 后的根路径与 normalize 结果比较时,大小写/长路径格式要一致,生产环境可再加 path.relative 判断是否含 ..
  • getFileInfo 聚合 basename/extname,供静态服务器设置响应头或日志;与 §6.1 文件服务器思路一致。

2.2 FS 文件系统模块

FS(File System)模块是 Node.js 中最重要的内置模块之一,提供了文件读写、目录操作等完整的文件系统 API。

FS 模块架构

#mermaid-svg-91kslx8wJhRCl5gi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-91kslx8wJhRCl5gi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-91kslx8wJhRCl5gi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-91kslx8wJhRCl5gi .error-icon{fill:#552222;}#mermaid-svg-91kslx8wJhRCl5gi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-91kslx8wJhRCl5gi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-91kslx8wJhRCl5gi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-91kslx8wJhRCl5gi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-91kslx8wJhRCl5gi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-91kslx8wJhRCl5gi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-91kslx8wJhRCl5gi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-91kslx8wJhRCl5gi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-91kslx8wJhRCl5gi .marker.cross{stroke:#333333;}#mermaid-svg-91kslx8wJhRCl5gi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-91kslx8wJhRCl5gi p{margin:0;}#mermaid-svg-91kslx8wJhRCl5gi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-91kslx8wJhRCl5gi .cluster-label text{fill:#333;}#mermaid-svg-91kslx8wJhRCl5gi .cluster-label span{color:#333;}#mermaid-svg-91kslx8wJhRCl5gi .cluster-label span p{background-color:transparent;}#mermaid-svg-91kslx8wJhRCl5gi .label text,#mermaid-svg-91kslx8wJhRCl5gi span{fill:#333;color:#333;}#mermaid-svg-91kslx8wJhRCl5gi .node rect,#mermaid-svg-91kslx8wJhRCl5gi .node circle,#mermaid-svg-91kslx8wJhRCl5gi .node ellipse,#mermaid-svg-91kslx8wJhRCl5gi .node polygon,#mermaid-svg-91kslx8wJhRCl5gi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-91kslx8wJhRCl5gi .rough-node .label text,#mermaid-svg-91kslx8wJhRCl5gi .node .label text,#mermaid-svg-91kslx8wJhRCl5gi .image-shape .label,#mermaid-svg-91kslx8wJhRCl5gi .icon-shape .label{text-anchor:middle;}#mermaid-svg-91kslx8wJhRCl5gi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-91kslx8wJhRCl5gi .rough-node .label,#mermaid-svg-91kslx8wJhRCl5gi .node .label,#mermaid-svg-91kslx8wJhRCl5gi .image-shape .label,#mermaid-svg-91kslx8wJhRCl5gi .icon-shape .label{text-align:center;}#mermaid-svg-91kslx8wJhRCl5gi .node.clickable{cursor:pointer;}#mermaid-svg-91kslx8wJhRCl5gi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-91kslx8wJhRCl5gi .arrowheadPath{fill:#333333;}#mermaid-svg-91kslx8wJhRCl5gi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-91kslx8wJhRCl5gi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-91kslx8wJhRCl5gi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-91kslx8wJhRCl5gi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-91kslx8wJhRCl5gi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-91kslx8wJhRCl5gi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-91kslx8wJhRCl5gi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-91kslx8wJhRCl5gi .cluster text{fill:#333;}#mermaid-svg-91kslx8wJhRCl5gi .cluster span{color:#333;}#mermaid-svg-91kslx8wJhRCl5gi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-91kslx8wJhRCl5gi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-91kslx8wJhRCl5gi rect.text{fill:none;stroke-width:0;}#mermaid-svg-91kslx8wJhRCl5gi .icon-shape,#mermaid-svg-91kslx8wJhRCl5gi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-91kslx8wJhRCl5gi .icon-shape p,#mermaid-svg-91kslx8wJhRCl5gi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-91kslx8wJhRCl5gi .icon-shape .label rect,#mermaid-svg-91kslx8wJhRCl5gi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-91kslx8wJhRCl5gi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-91kslx8wJhRCl5gi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-91kslx8wJhRCl5gi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} FS 文件系统模块
文件操作
目录操作
文件信息
流式操作
读取 readFile
写入 writeFile
追加 appendFile
删除 unlink
重命名 rename
创建 mkdir
删除 rmdir
读取 readdir
判断 exists/access
状态 stat
权限 chmod
读取流 createReadStream
写入流 createWriteStream

名词解释
  • 同步 vs 异步:同步操作会阻塞代码执行直到操作完成,异步操作不会阻塞,通过回调函数处理结果
  • Buffer:Node.js 中用于处理二进制数据的类,类似于数组但存储的是原始内存数据
  • 文件描述符:操作系统用于访问文件的数字标识符
  • 流(Stream):处理流式数据的抽象接口,可以逐块处理数据而不需要全部加载到内存
2.2.1 文件读取操作
1. 异步文件读取 (fs.readFile)

基础语法fs.readFile(path[, options], callback)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

const filePath = path.join(__dirname, 'data', 'example.txt');

fs.readFile(filePath, (err, data) => {
    if (err) {
        console.error('文件读取失败:', err.errno, err.code);
        return;
    }
    console.log('Buffer 数据:', data);
    console.log('字符串内容:', data.toString());
});

fs.readFile(filePath, 'utf-8', (err, content) => {
    if (err) {
        console.error('文件读取失败:', err.message);
        return;
    }
    console.log('文件内容:', content);
});

console.log('开始读取文件...');

【代码注释】

  • path.join(__dirname, 'data', 'example.txt') 保证无论从哪个目录执行 node,都读取脚本旁边 的 data 目录(课堂案例 datas/a.txt 同理)。
  • 不传编码时回调的 dataBuffer ,文本需 data.toString('utf-8');图片/二进制应保持 Buffer 直接写入或处理。
  • 'utf-8' 时 Node 在读完文件后自动解码为字符串,适合 .txt.json.md
  • console.log('开始读取...')先于 两个回调执行,说明 readFile 不阻塞事件循环;Web 服务中必须用异步,否则一个慢磁盘会卡住所有请求。
  • err.code 常见 ENOENT(路径错)、EACCES(无权限);务必 if (err) return,避免对 undefinedtoString

回调函数参数说明

  • err:错误对象,如果操作成功则为 null
  • data:文件内容(Buffer 或字符串,取决于是否指定编码)
2. 同步文件读取 (fs.readFileSync)

基础语法fs.readFileSync(path[, options])

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

const filePath = path.join(__dirname, 'data', 'example.txt');

try {
    const content = fs.readFileSync(filePath, 'utf-8');
    console.log('文件内容:', content);

    const buffer = fs.readFileSync(filePath);
    console.log('Buffer 长度:', buffer.length);
    console.log('前100字节:', buffer.toString('utf-8', 0, 100));

} catch (error) {
    console.error('文件读取失败:', error.message);
    console.error('错误代码:', error.code);
}

try {
    const configPath = path.join(__dirname, 'config.json');
    const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
    console.log('配置加载成功:', config);
} catch (error) {
    console.error('配置文件加载失败,使用默认配置');
}

【代码注释】

  • readFileSync 在读完前阻塞主线程;同一文件连续读两次会访问磁盘两遍,仅作演示,实际选一种即可。
  • buffer.toString('utf-8', 0, 100) 的第三、四参数是字节起止偏移,不是字符下标;含中文时勿按字符数截取。
  • 同步 API 失败时抛异常 ,用 try/catcherror.code 与异步回调里的 err.code 含义相同。
  • JSON.parse(readFileSync(...)) 是启动时加载 config.json 的经典写法;parse 失败会抛 SyntaxError,应单独 catch 或合并到同一 try
  • 下方 config 分支演示:文件缺失时用默认配置,避免进程退出。

同步 vs 异步选择指南

  • 使用异步:大文件操作、Web 服务、避免阻塞主线程
  • 使用同步:配置文件读取、启动脚本、小文件快速读取
文件路径:cwd__dirname(易错点)

fs.readFile('./datas/a.txt') 中的 ./ 相对的是启动 node 时的工作目录,不是脚本所在目录。

javascript 复制代码
const fs = require('fs');
const path = require('path');

// const filename = './datas/a.txt';

const filename = path.join(__dirname, './datas/a.txt');

console.log('读取路径:', filename);

fs.readFile(filename, 'utf-8', (err, data) => {
    if (err) {
        console.log('文件读取失败:', err.message);
        return;
    }
    console.log(data);
});

【代码注释】

  • ./datas/a.txt 相对的是启动 node 时的工作目录 cwd :在项目根执行 node src/app.js 会找「项目根/datas」,在 src 里执行则找「src/datas」,极易 ENOENT。
  • path.join(__dirname, './datas/a.txt') 相对当前模块文件所在目录,与从哪里执行命令无关,是课堂案例与生产部署的推荐写法。
  • path.resolve('./datas/a.txt') 同样相对 cwd,只是结果会变成绝对路径字符串;需要绝对路径且锚定脚本目录时应写 path.resolve(__dirname, 'datas/a.txt')
  • 打印 filename 便于调试:复制终端里的路径到资源管理器核对文件是否真实存在。
2.2.2 文件写入操作
1. 基础文件写入 (fs.writeFile)

基础语法fs.writeFile(file, data[, options], callback)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

const filePath = path.join(__dirname, 'output', 'log.txt');
const content = `日志记录 - ${new Date().toISOString()}\n`;

fs.writeFile(filePath, content, { encoding: 'utf-8' }, (err) => {
    if (err) {
        console.error('文件写入失败:', err.message);
        return;
    }
    console.log('文件写入成功!');

    fs.readFile(filePath, 'utf-8', (err, data) => {
        if (!err) {
            console.log('验证内容:', data);
        }
    });
});

try {
    fs.writeFileSync(filePath, content, 'utf-8');
    console.log('同步写入成功!');
} catch (error) {
    console.error('同步写入失败:', error.message);
}

【代码注释】

  • writeFile 默认 覆盖 已有文件;output 目录不存在时会 ENOENT,需先 mkdirSync(..., { recursive: true })
  • 异步写入完成后才适合 readFile 验证;若两次 writeFile 几乎同时发起,后完成的会覆盖先完成的。
  • encoding: 'utf-8' 可把字符串按 UTF-8 编码写入;也可直接写 Buffer(二进制文件)。
  • 同步 writeFileSync 会阻塞到落盘结束,CLI 小文件可以,高并发服务应改用异步或 Stream。

写入模式选项

javascript 复制代码
const fs = require('fs');

const writeOptions = {
    'w': '写入模式,默认值,覆盖文件内容',
    'wx': '独占写入,文件存在则失败',
    'a': '追加模式,在文件末尾添加内容',
    'ax': '独占追加,文件不存在则创建'
};

fs.writeFile('example.txt', '内容', { flag: 'w' }, (err) => {
    if (err) console.error(err);
});

【代码注释】

  • flag: 'w' 截断并覆盖;wx 用于「原子创建」:文件已存在则报错,避免误覆盖重要配置。
  • a / axappendFile 类似,在文件尾追加;日志场景常用 a,新文件用 ax 可防止重复初始化时覆盖旧日志。
  • flag 也可用于 createWriteStream({ flags: 'a' }),与 writeFile 语义一致。
2. 文件追加内容 (fs.appendFile)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

const logFile = path.join(__dirname, 'logs', 'app.log');

const logMessage = `[${new Date().toISOString()}] 用户登录成功\n`;

fs.appendFile(logFile, logMessage, (err) => {
    if (err) {
        console.error('日志写入失败:', err.message);
    } else {
        console.log('日志追加成功');
    }
});

try {
    const logEntries = [];
    for (let i = 0; i < 1000; i++) {
        logEntries.push(`[Log ${i}] ${new Date().toISOString()}\n`);
    }
    fs.appendFileSync(logFile, logEntries.join(''));
    console.log('批量日志写入成功');
} catch (error) {
    console.error('批量写入失败:', error.message);
}

【代码注释】

  • appendFile 在文件末尾追加,不截断原内容;文件不存在时会创建新文件(目录仍须存在)。
  • 单行异步追加适合访问日志;循环 1000 次 appendFileSync 会触发 1000 次磁盘操作,性能差。
  • 示例先在内存 join 再一次 appendFileSync,与 Day06「批量再写」优化思路相同;更高吞吐用 createWriteStream({ flags: 'a' }) 保持句柄打开。
1. 文件重命名和移动 (fs.rename)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

const oldName = path.join(__dirname, 'data.txt');
const newName = path.join(__dirname, 'data_backup.txt');

fs.rename(oldName, newName, (err) => {
    if (err) {
        console.error('重命名失败:', err.message);
    } else {
        console.log('文件重命名成功!');
    }
});

const sourceFile = path.join(__dirname, 'uploads', 'image.png');
const targetFile = path.join(__dirname, 'public', 'images', 'logo.png');
const targetDir = path.dirname(targetFile);

fs.mkdir(targetDir, { recursive: true }, (err) => {
    if (!err) {
        fs.rename(sourceFile, targetFile, (err) => {
            if (err) {
                console.error('文件移动失败:', err.message);
            } else {
                console.log('文件移动成功!');
            }
        });
    }
});

【代码注释】

  • rename(old, new) 在同一磁盘分区内通常只改目录项,不复制 数据,故既可改扩展名(a.txta.md)也可跨文件夹「移动」。
  • 目标文件已存在时多数系统会覆盖;重要文件应先备份。
  • 移动前先 mkdir(targetDir, { recursive: true }),否则目标路径父目录不存在会 ENOENT
  • 跨设备移动可能报 EXDEV,需 copyFile + unlink 组合。
2. 文件删除 (fs.unlink)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

const fileToDelete = path.join(__dirname, 'temp.txt');

fs.unlink(fileToDelete, (err) => {
    if (err) {
        if (err.code === 'ENOENT') {
            console.log('文件不存在,无需删除');
        } else {
            console.error('文件删除失败:', err.message);
        }
    } else {
        console.log('文件删除成功!');
    }
});

const filesToDelete = ['temp1.txt', 'temp2.txt', 'temp3.txt'];

const deletePromises = filesToDelete.map(file => {
    return new Promise((resolve, reject) => {
        fs.unlink(file, (err) => {
            if (err) reject(err);
            else resolve();
        });
    });
});

Promise.all(deletePromises)
    .then(() => console.log('所有文件删除成功'))
    .catch(err => console.error('批量删除失败:', err.message));

【代码注释】

  • unlink 只删文件 ,不能删非空目录;课堂流程「改名 → 再删」对应 renameunlink
  • ENOENT 表示路径不存在,幂等删除场景可视为成功,不必抛错给用户。
  • Promise.all 并行删除:任一失败则整个 catch;若需「尽量删完」应改用 Promise.allSettled
  • 批量路径示例为相对 cwd 的文件名,生产环境应 path.join(__dirname, file)
3. 目录操作 (mkdir, rmdir, readdir)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

const dirPath = path.join(__dirname, 'projects', 'web', 'src', 'components');

fs.mkdir(dirPath, { recursive: true }, (err) => {
    if (err) {
        console.error('目录创建失败:', err.message);
    } else {
        console.log('目录结构创建成功!');
    }
});

const readDir = path.join(__dirname, 'projects');

fs.readdir(readDir, (err, files) => {
    if (err) {
        console.error('目录读取失败:', err.message);
        return;
    }
    console.log('目录内容:');
    files.forEach((file, index) => {
        console.log(`${index + 1}. ${file}`);
    });
});

const dirToDelete = path.join(__dirname, 'old_projects');

fs.rmdir(dirToDelete, { recursive: true }, (err) => {
    if (err) {
        if (err.code === 'ENOENT') {
            console.log('目录不存在,无需删除');
        } else {
            console.error('目录删除失败:', err.message);
        }
    } else {
        console.log('目录删除成功!');
    }
});

【代码注释】

  • mkdir(..., { recursive: true }) 等价 mkdir -p,一次创建 projects/web/src/components 多级目录。
  • readdir 只列当前层 文件名,不含子目录内部;要区分文件/目录需对每个条目再 stat 或使用 readdirwithFileTypes 选项(Node 10+)。
  • rmdir(..., { recursive: true }) 递归删除整棵树;Node 14.14+ 也可用 fs.rm(path, { recursive: true, force: true })
  • 删除非空目录若不用 recursive 会报 ENOTEMPTY
4. 文件状态和判断 (fs.stat, fs.access)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

const targetPath = path.join(__dirname, 'example.txt');

fs.stat(targetPath, (err, stats) => {
    if (err) {
        console.error('获取文件信息失败:', err.message);
        return;
    }
    console.log('文件信息:');
    console.log('- 是否为文件:', stats.isFile());
    console.log('- 是否为目录:', stats.isDirectory());
    console.log('- 文件大小:', stats.size, '字节');
    console.log('- 创建时间:', stats.birthtime);
    console.log('- 修改时间:', stats.mtime);
});

fs.access(targetPath, fs.constants.F_OK, (err) => {
    console.log(err ? '文件或目录不存在' : '文件或目录存在');
});

fs.access(targetPath, fs.constants.R_OK | fs.constants.W_OK, (err) => {
    console.log(err ? '没有读写权限' : '有读写权限');
});

【代码注释】

  • stat 返回 fs.StatsisFile() / isDirectory() 决定用 readFile 还是 readdir;对目录 readFileEISDIR
  • size 为字节数;mtime 常用于缓存失效、日志轮转判断。
  • access(F_OK) 仅判断存在;R_OK | W_OK 用位或组合检测读+写权限,失败不一定是「不存在」,也可能是权限不足。
  • existsSync 简单但同步阻塞;高并发服务优先异步 access/stat
2.2.4 流式文件操作

流式操作优势

  • 内存效率:不需要将整个文件加载到内存
  • 时间效率:开始处理数据更快
  • 管道机制:可以连接多个流操作

#mermaid-svg-NMHe3znrzWVJk92V{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NMHe3znrzWVJk92V .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NMHe3znrzWVJk92V .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NMHe3znrzWVJk92V .error-icon{fill:#552222;}#mermaid-svg-NMHe3znrzWVJk92V .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NMHe3znrzWVJk92V .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NMHe3znrzWVJk92V .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NMHe3znrzWVJk92V .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NMHe3znrzWVJk92V .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NMHe3znrzWVJk92V .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NMHe3znrzWVJk92V .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NMHe3znrzWVJk92V .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NMHe3znrzWVJk92V .marker.cross{stroke:#333333;}#mermaid-svg-NMHe3znrzWVJk92V svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NMHe3znrzWVJk92V p{margin:0;}#mermaid-svg-NMHe3znrzWVJk92V .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NMHe3znrzWVJk92V .cluster-label text{fill:#333;}#mermaid-svg-NMHe3znrzWVJk92V .cluster-label span{color:#333;}#mermaid-svg-NMHe3znrzWVJk92V .cluster-label span p{background-color:transparent;}#mermaid-svg-NMHe3znrzWVJk92V .label text,#mermaid-svg-NMHe3znrzWVJk92V span{fill:#333;color:#333;}#mermaid-svg-NMHe3znrzWVJk92V .node rect,#mermaid-svg-NMHe3znrzWVJk92V .node circle,#mermaid-svg-NMHe3znrzWVJk92V .node ellipse,#mermaid-svg-NMHe3znrzWVJk92V .node polygon,#mermaid-svg-NMHe3znrzWVJk92V .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NMHe3znrzWVJk92V .rough-node .label text,#mermaid-svg-NMHe3znrzWVJk92V .node .label text,#mermaid-svg-NMHe3znrzWVJk92V .image-shape .label,#mermaid-svg-NMHe3znrzWVJk92V .icon-shape .label{text-anchor:middle;}#mermaid-svg-NMHe3znrzWVJk92V .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NMHe3znrzWVJk92V .rough-node .label,#mermaid-svg-NMHe3znrzWVJk92V .node .label,#mermaid-svg-NMHe3znrzWVJk92V .image-shape .label,#mermaid-svg-NMHe3znrzWVJk92V .icon-shape .label{text-align:center;}#mermaid-svg-NMHe3znrzWVJk92V .node.clickable{cursor:pointer;}#mermaid-svg-NMHe3znrzWVJk92V .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NMHe3znrzWVJk92V .arrowheadPath{fill:#333333;}#mermaid-svg-NMHe3znrzWVJk92V .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NMHe3znrzWVJk92V .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NMHe3znrzWVJk92V .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NMHe3znrzWVJk92V .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NMHe3znrzWVJk92V .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NMHe3znrzWVJk92V .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NMHe3znrzWVJk92V .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NMHe3znrzWVJk92V .cluster text{fill:#333;}#mermaid-svg-NMHe3znrzWVJk92V .cluster span{color:#333;}#mermaid-svg-NMHe3znrzWVJk92V div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NMHe3znrzWVJk92V .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NMHe3znrzWVJk92V rect.text{fill:none;stroke-width:0;}#mermaid-svg-NMHe3znrzWVJk92V .icon-shape,#mermaid-svg-NMHe3znrzWVJk92V .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NMHe3znrzWVJk92V .icon-shape p,#mermaid-svg-NMHe3znrzWVJk92V .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NMHe3znrzWVJk92V .icon-shape .label rect,#mermaid-svg-NMHe3znrzWVJk92V .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NMHe3znrzWVJk92V .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NMHe3znrzWVJk92V .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NMHe3znrzWVJk92V :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 文件读取流
处理中间件
文件写入流
数据压缩
数据加密

1. 创建读取流 (createReadStream)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

// 【代码注释】创建大文件读取流
const sourceFile = path.join(__dirname, 'large-file.txt');
const readStream = fs.createReadStream(sourceFile, {
    // 【代码注释】设置编码
    encoding: 'utf-8',
    // 【代码注释】设置缓冲区大小(64KB)
    highWaterMark: 64 * 1024,
    // 【代码注释】设置起始位置
    start: 0,
    // 【代码注释】设置结束位置
    end: undefined
});

// 【代码注释】监听数据事件
readStream.on('data', (chunk) => {
    console.log('接收到数据块,大小:', chunk.length);
    console.log('数据块内容:', chunk.substring(0, 100) + '...');
});

// 【代码注释】监听结束事件
readStream.on('end', () => {
    console.log('文件读取完毕!');
});

// 【代码注释】监听错误事件
readStream.on('error', (err) => {
    console.error('文件读取错误:', err.message);
});

// 【代码注释】监听打开事件
readStream.on('open', (fd) => {
    console.log('文件已打开,文件描述符:', fd);
});

【代码注释】

  • createReadStreamhighWaterMark(默认 64KB)分块触发 data 事件,内存占用近似常量,适合大文件。
  • encoding: 'utf-8'chunk 为字符串,否则为 Buffer;start/end 可只读文件片段(断点续传、分片下载)。
  • 必须监听 error,否则磁盘错误可能导致进程未处理异常;end 表示数据读完,close 表示描述符关闭。
2. 创建写入流 (createWriteStream)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');

// 【代码注释】创建文件写入流
const targetFile = path.join(__dirname, 'output.txt');
const writeStream = fs.createWriteStream(targetFile, {
    // 【代码注释】设置编码
    encoding: 'utf-8',
    // 【代码注释】设置标志位
    flags: 'w',
    // 【代码注释】设置权限
    mode: 0o666,
    // 【代码注释】设置缓冲区大小
    highWaterMark: 16 * 1024
});

// 【代码注释】监听准备就绪事件
writeStream.on('ready', () => {
    console.log('写入流已准备就绪');
    
    // 【代码注释】写入数据
    for (let i = 0; i < 100; i++) {
        const canWrite = writeStream.write(`行 ${i}: ${Date.now()}\n`);
        
        // 【代码注释】检查缓冲区是否已满
        if (!canWrite) {
            console.log('缓冲区已满,等待 drain 事件');
        }
    }
    
    // 【代码注释】结束写入
    writeStream.end();
});

// 【代码注释】监听 drain 事件(缓冲区清空)
writeStream.on('drain', () => {
    console.log('缓冲区已清空,可以继续写入');
});

// 【代码注释】监听完成事件
writeStream.on('finish', () => {
    console.log('所有数据已写入完成!');
});

// 【代码注释】监听关闭事件
writeStream.on('close', () => {
    console.log('文件已关闭');
});

// 【代码注释】监听错误事件
writeStream.on('error', (err) => {
    console.error('写入错误:', err.message);
});

【代码注释】

  • write() 返回 false 表示内部缓冲已满,应停止写入并等待 drain 再继续,否则内存暴涨(背压机制)。
  • end() 表示不再写入新数据,冲刷缓冲后触发 finishclose 表示 fd 已关闭。
  • flags: 'w' 覆盖写;日志追加用 flags: 'a'mode: 0o666 控制新建文件权限(受 umask 影响)。
3. 管道操作 (pipe)

完整示例代码

javascript 复制代码
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');

// 【代码注释】文件复制
const sourceFile = path.join(__dirname, 'source.txt');
const targetFile = path.join(__dirname, 'target.txt');

const readStream = fs.createReadStream(sourceFile);
const writeStream = fs.createWriteStream(targetFile);

// 【代码注释】使用管道进行文件复制
readStream.pipe(writeStream);

console.log('开始文件复制...');

// 【代码注释】监听复制完成
writeStream.on('finish', () => {
    console.log('文件复制完成!');
});

// 【代码注释】文件压缩示例
const inputFile = path.join(__dirname, 'large-file.txt');
const compressedFile = path.join(__dirname, 'large-file.txt.gz');

const input = fs.createReadStream(inputFile);
const gzip = zlib.createGzip();
const output = fs.createWriteStream(compressedFile);

// 【代码注释】管道链:读取 -> 压缩 -> 写入
input.pipe(gzip).pipe(output);

output.on('finish', () => {
    console.log('文件压缩完成!');
});

// 【代码注释】文件解压示例
const decompressedFile = path.join(__dirname, 'decompressed.txt');

const gzInput = fs.createReadStream(compressedFile);
const gunzip = zlib.createGunzip();
const decompressedOutput = fs.createWriteStream(decompressedFile);

gzInput.pipe(gunzip).pipe(decompressedOutput);

decompressedOutput.on('finish', () => {
    console.log('文件解压完成!');
});

【代码注释】

  • readStream.pipe(writeStream) 自动处理背压,是文件复制的标准写法;finish 在数据全部写入后触发。
  • input.pipe(gzip).pipe(output) 形成管道链:读 → 压缩 → 写,全程不落完整文件到内存。
  • 解压链 gunzip 顺序相反;任一流 error 应销毁整条链(pipeline API 更安全,Node 10+)。
  • 课堂「流式案例」:复制大文件、打包日志、上传下载代理都基于同一模型。
4. 流式写入大批量行(createWriteStream + close
javascript 复制代码
const fs = require('fs');
const path = require('path');

const file = path.join(__dirname, 'output', 'big.log');
const ws = fs.createWriteStream(file);

ws.on('close', () => {
    console.log('写入完毕!');
});

for (let i = 0; i < 100000; i++) {
    ws.write(`${i} ${Math.random()} ${Date.now()}\n`);
}

ws.close();

【代码注释】

  • createWriteStream 内部有缓冲区;write 返回 false 表示应暂停写入并监听 drain,示例中 10 万次循环在极高吞吐下需处理背压。
  • 必须调用 ws.close()(或 end())才会刷新剩余缓冲并关闭文件描述符,触发 close 事件;只 writeclose 可能导致数据未完全落盘。
  • 课堂案例对比:appendFileSync 万次同步 I/O 极慢;流式 + 单次打开文件句柄是写大日志的正确方向。
  • 复制文件优先 readStream.pipe(writeStream),不必把整文件读入内存。
2.2.5 Promise API 和 fs.promises

从 Node.js 10 开始,fs 模块提供了基于 Promise 的 API,使异步操作更加优雅。

完整示例代码

javascript 复制代码
const fs = require('fs').promises;
const path = require('path');

// 【代码注释】使用 async/await 读取文件
async function readFileAsync(filePath) {
    try {
        const content = await fs.readFile(filePath, 'utf-8');
        console.log('文件内容:', content);
        return content;
    } catch (error) {
        console.error('文件读取失败:', error.message);
        throw error;
    }
}

// 【代码注释】使用 async/await 写入文件
async function writeFileAsync(filePath, content) {
    try {
        await fs.writeFile(filePath, content, 'utf-8');
        console.log('文件写入成功');
    } catch (error) {
        console.error('文件写入失败:', error.message);
        throw error;
    }
}

// 【代码注释】使用 Promise.all 并行处理多个文件
async function processMultipleFiles() {
    const files = [
        'file1.txt',
        'file2.txt',
        'file3.txt'
    ];
    
    try {
        // 【代码注释】并行读取多个文件
        const contents = await Promise.all(
            files.map(file => fs.readFile(file, 'utf-8'))
        );
        
        console.log('所有文件读取完成');
        return contents;
    } catch (error) {
        console.error('批量文件处理失败:', error.message);
    }
}

// 【代码注释】综合示例:文件处理流程
async function processFileWorkflow() {
    const inputPath = path.join(__dirname, 'input.txt');
    const outputPath = path.join(__dirname, 'output.txt');
    
    try {
        // 【代码注释】1. 检查输入文件是否存在
        await fs.access(inputPath);
        
        // 【代码注释】2. 读取文件内容
        const content = await fs.readFile(inputPath, 'utf-8');
        
        // 【代码注释】3. 处理内容
        const processedContent = content.toUpperCase();
        
        // 【代码注释】4. 写入输出文件
        await fs.writeFile(outputPath, processedContent);
        
        console.log('文件处理流程完成');
        
    } catch (error) {
        if (error.code === 'ENOENT') {
            console.log('输入文件不存在');
        } else {
            console.error('文件处理失败:', error.message);
        }
    }
}

【代码注释】

  • fs.promises 与回调 API 等价,但返回 Promise,适合 async/await 写线性流程。
  • Promise.all(files.map(...)) 并行 读多个文件,任一失败则整体 reject;需「部分成功」用 allSettled
  • processFileWorkflowaccess 检查存在 → readFile → 处理 → writeFile,是 CLI/ETL 脚本的典型管道;ENOENT 单独分支提示用户。
  • throw error 在工具函数里可把错误交给上层统一处理;勿在 Web 中间件里吞掉异常。

2.3 URL 模块

URL 模块用于处理和解析 URL 字符串,是 Web 开发中必不可少的工具。

核心功能
javascript 复制代码
const url = require('url');

// 【代码注释】示例 URL
const siteAddress = 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash';

// 【代码注释】方法一:使用 url.parse()(传统方法)
const urlData = url.parse(siteAddress);
console.log('解析结果:', urlData);
/* 输出结构:
{
  protocol: 'https:',
  slashes: true,
  auth: 'user:pass',
  host: 'sub.example.com:8080',
  port: '8080',
  hostname: 'sub.example.com',
  hash: '#hash',
  search: '?query=string',
  query: 'query=string',
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash'
}
*/

// 【代码注释】方法二:使用 URL 类(推荐,WHATWG 标准)
const urlInfo = new URL(siteAddress);
console.log('现代 URL 解析:');
console.log('- 协议:', urlInfo.protocol);
console.log('- 主机名:', urlInfo.hostname);
console.log('- 端口:', urlInfo.port);
console.log('- 路径:', urlInfo.pathname);
console.log('- 查询参数:', urlInfo.search);
console.log('- 哈希:', urlInfo.hash);
console.log('- 用户信息:', urlInfo.username, urlInfo.password);

// 【代码注释】处理查询参数
console.log('查询参数详情:');
urlInfo.searchParams.forEach((value, key) => {
    console.log(`${key}: ${value}`);
});

【代码注释】

  • url.parse(str)(传统 API)返回带 protocolpathnamequery 等字段的对象;query 仍是字符串,需再 querystring.parse
  • new URL(str) 是 WHATWG 标准,与浏览器一致;searchParams 提供 get/set/forEach,推荐新项目使用。
  • protocol 含尾部的 :(如 https:);hostname 不含端口,host 含端口。
  • 解析相对 URL 需基址:new URL('/api/users', 'https://example.com')
URL 模块实战应用
javascript 复制代码
const url = require('url');

// 【实战案例】构建 URL 处理工具类
class URLProcessor {
    // 【代码注释】解析 URL 并提取关键信息
    static parseURL(urlString) {
        try {
            const parsedUrl = new URL(urlString);
            
            return {
                protocol: parsedUrl.protocol.replace(':', ''),
                hostname: parsedUrl.hostname,
                port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? '443' : '80'),
                pathname: parsedUrl.pathname,
                searchParams: this.getSearchParams(parsedUrl),
                hash: parsedUrl.hash.replace('#', '')
            };
        } catch (error) {
            throw new Error(`无效的 URL: ${urlString}`);
        }
    }
    
    // 【代码注释】提取查询参数
    static getSearchParams(urlObj) {
        const params = {};
        urlObj.searchParams.forEach((value, key) => {
            params[key] = value;
        });
        return params;
    }
    
    // 【代码注释】构建 URL
    static buildURL(baseURL, path, params = {}) {
        const urlObj = new URL(path, baseURL);
        
        Object.entries(params).forEach(([key, value]) => {
            urlObj.searchParams.set(key, value);
        });
        
        return urlObj.toString();
    }
}

// 使用示例
const testURL = 'https://api.example.com/users?page=1&limit=10&sort=name';
const parsed = URLProcessor.parseURL(testURL);
console.log('解析结果:', parsed);

const builtURL = URLProcessor.buildURL(
    'https://api.example.com',
    '/search',
    { q: 'nodejs', lang: 'zh' }
);
console.log('构建的 URL:', builtURL);

【代码注释】

  • parseURLnew URL 包在 try/catch 里,无效 URL 转业务错误,避免进程崩溃。
  • searchParams.forEach 把查询串转为普通对象,便于与 Express 的 req.query 对照。
  • buildURL(baseURL, path, params)new URL(path, baseURL) 解析相对路径;searchParams.set 自动编码特殊字符。
  • 默认端口:HTTPS 空 port 时示例补 443,HTTP 补 80,与浏览器行为一致。

2.4 QueryString 模块

QueryString 模块用于解析和格式化 URL 查询字符串,虽然很多功能已被 URL 模块取代,但仍在维护旧项目时有价值。

核心方法
javascript 复制代码
const querystring = require('querystring');

// 【代码注释】解析查询字符串为对象
const queryString = 'name=John&age=30&city=New%20York';
const parsed = querystring.parse(queryString);
console.log('解析结果:', parsed);
// 输出: { name: 'John', age: '30', city: 'New York' }

// 【代码注释】将对象序列化为查询字符串
const params = {
    search: 'nodejs tutorial',
    page: 1,
    limit: 10,
    filters: ['beginner', 'video']
};

const serialized = querystring.stringify(params);
console.log('序列化结果:', serialized);
// 输出: 'search=nodejs%20tutorial&page=1&limit=10&filters=beginner&filters=video'

// 【代码注释】自定义分隔符
const customParsed = querystring.parse('a=1;b=2;c=3', ';', '=');
console.log('自定义分隔符:', customParsed);

// 【代码注释】转义特殊字符
const escaped = querystring.escape('hello world');
console.log('转义结果:', escaped); // 'hello%20world'

const unescaped = querystring.unescape('hello%20world');
console.log('反转义结果:', unescaped);

【代码注释】

  • parse('name=John&age=30') 得到对象,所有值为字符串age'30' 而非数字),计算前需 Number()parseInt
  • stringify 会对空格等编码为 %20;数组字段 filters 会生成重复键 filters=a&filters=b
  • 第二、三个参数可自定义分隔符,如 ';''=',用于解析非标准日志格式。
  • 新代码优先 URLSearchParams;维护旧项目、阅读老 Express 代码时仍会见到 querystring

3. 异常处理机制

3.1 错误对象基础

Node.js 中的错误处理遵循 JavaScript 标准,提供了一套完整的错误处理机制。

Error 对象结构

#mermaid-svg-76LnupqiNKJgqn4M{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-76LnupqiNKJgqn4M .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-76LnupqiNKJgqn4M .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-76LnupqiNKJgqn4M .error-icon{fill:#552222;}#mermaid-svg-76LnupqiNKJgqn4M .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-76LnupqiNKJgqn4M .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-76LnupqiNKJgqn4M .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-76LnupqiNKJgqn4M .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-76LnupqiNKJgqn4M .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-76LnupqiNKJgqn4M .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-76LnupqiNKJgqn4M .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-76LnupqiNKJgqn4M .marker{fill:#333333;stroke:#333333;}#mermaid-svg-76LnupqiNKJgqn4M .marker.cross{stroke:#333333;}#mermaid-svg-76LnupqiNKJgqn4M svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-76LnupqiNKJgqn4M p{margin:0;}#mermaid-svg-76LnupqiNKJgqn4M .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-76LnupqiNKJgqn4M .cluster-label text{fill:#333;}#mermaid-svg-76LnupqiNKJgqn4M .cluster-label span{color:#333;}#mermaid-svg-76LnupqiNKJgqn4M .cluster-label span p{background-color:transparent;}#mermaid-svg-76LnupqiNKJgqn4M .label text,#mermaid-svg-76LnupqiNKJgqn4M span{fill:#333;color:#333;}#mermaid-svg-76LnupqiNKJgqn4M .node rect,#mermaid-svg-76LnupqiNKJgqn4M .node circle,#mermaid-svg-76LnupqiNKJgqn4M .node ellipse,#mermaid-svg-76LnupqiNKJgqn4M .node polygon,#mermaid-svg-76LnupqiNKJgqn4M .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-76LnupqiNKJgqn4M .rough-node .label text,#mermaid-svg-76LnupqiNKJgqn4M .node .label text,#mermaid-svg-76LnupqiNKJgqn4M .image-shape .label,#mermaid-svg-76LnupqiNKJgqn4M .icon-shape .label{text-anchor:middle;}#mermaid-svg-76LnupqiNKJgqn4M .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-76LnupqiNKJgqn4M .rough-node .label,#mermaid-svg-76LnupqiNKJgqn4M .node .label,#mermaid-svg-76LnupqiNKJgqn4M .image-shape .label,#mermaid-svg-76LnupqiNKJgqn4M .icon-shape .label{text-align:center;}#mermaid-svg-76LnupqiNKJgqn4M .node.clickable{cursor:pointer;}#mermaid-svg-76LnupqiNKJgqn4M .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-76LnupqiNKJgqn4M .arrowheadPath{fill:#333333;}#mermaid-svg-76LnupqiNKJgqn4M .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-76LnupqiNKJgqn4M .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-76LnupqiNKJgqn4M .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-76LnupqiNKJgqn4M .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-76LnupqiNKJgqn4M .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-76LnupqiNKJgqn4M .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-76LnupqiNKJgqn4M .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-76LnupqiNKJgqn4M .cluster text{fill:#333;}#mermaid-svg-76LnupqiNKJgqn4M .cluster span{color:#333;}#mermaid-svg-76LnupqiNKJgqn4M div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-76LnupqiNKJgqn4M .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-76LnupqiNKJgqn4M rect.text{fill:none;stroke-width:0;}#mermaid-svg-76LnupqiNKJgqn4M .icon-shape,#mermaid-svg-76LnupqiNKJgqn4M .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-76LnupqiNKJgqn4M .icon-shape p,#mermaid-svg-76LnupqiNKJgqn4M .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-76LnupqiNKJgqn4M .icon-shape .label rect,#mermaid-svg-76LnupqiNKJgqn4M .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-76LnupqiNKJgqn4M .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-76LnupqiNKJgqn4M .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-76LnupqiNKJgqn4M :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Error 对象
message: 错误信息
name: 错误名称
stack: 堆栈信息
code: 错误代码

错误对象创建
javascript 复制代码
// 【代码注释】创建基础错误对象
const basicError = new Error('这是一个基本错误');
console.log('错误信息:', basicError.message);
console.log('错误名称:', basicError.name);

// 【代码注释】创建带有错误代码的错误
class FileSystemError extends Error {
    constructor(message, code) {
        super(message);
        this.name = 'FileSystemError';
        this.code = code;
    }
}

const fsError = new FileSystemError('文件不存在', 'ENOENT');
console.log('自定义错误:', fsError);

// 【代码注释】常见内置错误类型
try {
    // 【代码注释】语法错误
    // const badSyntax = ; // SyntaxError
    
    // 【代码注释】类型错误
    const num = 42;
    // num.toUpperCase(); // TypeError
    
    // 【代码注释】引用错误
    // nonexistentFunction(); // ReferenceError
    
    // 【代码注释】范围错误
    // new Array(-1); // RangeError
    
} catch (error) {
    console.error('捕获到错误:', error.name, error.message);
}

3.2 throw 抛出错误

javascript 复制代码
// 【代码注释】主动抛出错误示例
function divide(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new TypeError('参数必须是数字');
    }
    
    if (b === 0) {
        throw new Error('除数不能为零');
    }
    
    return a / b;
}

// 【代码注释】测试除法函数
try {
    console.log('10 ÷ 2 =', divide(10, 2));
    console.log('10 ÷ 0 =', divide(10, 0));
} catch (error) {
    console.error('计算错误:', error.message);
}

// 【代码注释】条件抛出错误
function processUserInput(input) {
    if (!input) {
        throw new Error('输入不能为空');
    }
    
    if (input.length < 3) {
        throw new Error('输入长度不能少于3个字符');
    }
    
    return input.toUpperCase();
}

// 【代码注释】抛出不同类型的错误
function validateConfig(config) {
    if (!config) {
        throw new Error('配置对象不能为空');
    }
    
    if (typeof config !== 'object') {
        throw new TypeError('配置必须是对象');
    }
    
    if (!config.port || isNaN(config.port)) {
        throw new Error('端口号无效');
    }
    
    return true;
}

3.3 try-catch-finally 结构

javascript 复制代码
// 【代码注释】完整的错误处理结构
function completeErrorHandling() {
    try {
        // 【代码注释】可能出错的代码
        console.log('开始执行...');
        
        // 模拟一个错误
        throw new Error('模拟的错误');
        
        console.log('这行代码不会执行');
        
    } catch (error) {
        // 【代码注释】错误处理代码
        console.error('捕获到错误:', error.message);
        console.error('错误类型:', error.name);
        
        // 【代码注释】根据错误类型进行不同处理
        if (error instanceof TypeError) {
            console.log('类型错误,请检查参数类型');
        } else if (error instanceof ReferenceError) {
            console.log('引用错误,请检查变量名');
        } else {
            console.log('其他错误:', error.message);
        }
        
    } finally {
        // 【代码注释】无论是否出错都会执行的代码
        console.log('清理资源...');
        console.log('执行完成');
    }
}

completeErrorHandling();

// 【代码注释】嵌套 try-catch 示例
function nestedTryCatch() {
    try {
        console.log('外层 try 开始');
        
        try {
            console.log('内层 try 开始');
            throw new Error('内层错误');
        } catch (innerError) {
            console.log('内层错误处理:', innerError.message);
            // 【代码注释】可以重新抛出错误
            throw innerError;
        }
        
    } catch (outerError) {
        console.log('外层错误处理:', outerError.message);
    }
}

nestedTryCatch();

try/catch 三条归纳(务必记住):

  1. try 内无论是运行时错误 还是 throw 主动抛出 ,都会被 catch 捕获,由开发者处理,进程通常不会因此直接崩溃。
  2. 有无异常,try/catch 之后的语句仍会执行 (除非你在 catchprocess.exit)。
  3. try 内出错位置之后的代码不会执行 ;需要「出错也继续」时拆成多个 try 或提前 return

【代码注释】

  • fs.readFile(path, (err) => { throw new Error() }) 中外层 try/catch 捕不到回调里的异常,因为回调在之后的事件循环才执行。
  • 正确做法:在回调里 if (err) 处理,或使用 async/await + fs.promises,让错误以 Promise rejection 形式进入 try/catch
  • process.on('uncaughtException') 不应代替业务错误处理;未捕获的异步错误会导致进程不稳定。
错误处理最佳实践
javascript 复制代码
// 【实战案例】异步操作错误处理
class AsyncErrorHandler {
    // 【代码注释】异步函数错误处理
    static async readFileWithErrorHandling(filePath) {
        try {
            const fs = require('fs').promises;
            const content = await fs.readFile(filePath, 'utf-8');
            return { success: true, data: content };
        } catch (error) {
            // 【代码注释】根据错误代码返回不同的错误信息
            if (error.code === 'ENOENT') {
                return { success: false, error: '文件不存在', code: 'FILE_NOT_FOUND' };
            } else if (error.code === 'EACCES') {
                return { success: false, error: '没有访问权限', code: 'ACCESS_DENIED' };
            } else {
                return { success: false, error: error.message, code: 'UNKNOWN_ERROR' };
            }
        }
    }
    
    // 【代码注释】批量操作错误处理
    static async processBatch(items) {
        const results = [];
        const errors = [];
        
        for (const item of items) {
            try {
                const result = await this.processItem(item);
                results.push(result);
            } catch (error) {
                errors.push({ item, error: error.message });
                // 【代码注释】继续处理下一个项目,不中断整个流程
            }
        }
        
        return { results, errors, totalCount: items.length };
    }
    
    static async processItem(item) {
        // 【代码注释】模拟项目处理
        if (Math.random() > 0.7) {
            throw new Error('随机处理失败');
        }
        return `处理结果: ${item}`;
    }
}

// 使用示例
async function testAsyncErrorHandler() {
    const handler = AsyncErrorHandler;
    
    // 测试文件读取
    const fileResult = await handler.readFileWithErrorHandling('nonexistent.txt');
    console.log('文件读取结果:', fileResult);
    
    // 测试批量处理
    const items = ['item1', 'item2', 'item3', 'item4', 'item5'];
    const batchResult = await handler.processBatch(items);
    console.log('批量处理结果:', batchResult);
}

testAsyncErrorHandler();

4. JSON 数据处理

4.1 JSON 格式详解

JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。

JSON 与 JavaScript 对象的区别
javascript 复制代码
// 【代码注释】JavaScript 对象(有效)
const jsObject = {
    name: 'John',
    age: 30,
    isAdmin: true,
    hobbies: ['reading', 'coding'],
    address: {
        city: 'New York',
        country: 'USA'
    },
    greet: function() {     // JavaScript 对象可以包含函数
        return 'Hello!';
    },
    lastName: 'Doe',        // 最后一个属性后面可以有逗号
};

// 【代码注释】JSON 格式(必须严格遵守规范)
const jsonString = `{
    "name": "John",         // 属性名必须用双引号
    "age": 30,              // 数字不需要引号
    "isAdmin": true,        // 布尔值不需要引号
    "hobbies": [            // 数组
        "reading",
        "coding"
    ],
    "address": {            // 嵌套对象
        "city": "New York",
        "country": "USA"
    }
    // 注意:不能包含函数
    // 注意:最后一个属性后面不能有逗号
}`;

console.log('JSON 字符串:', jsonString);

JSON 语法硬性规则(考试/联调常踩坑):

规则 说明
字符串必须双引号 'a' 在 JSON 文本中非法
属性名必须双引号 { name: 1 } 不是合法 JSON
最后一项无尾逗号 [1,2,] 解析失败
不能有函数/表达式 值只能是 JSON 支持的六种类型

【代码注释】

  • JSON 文本 中属性名、字符串必须用双引号;不能有函数、注释、尾逗号(与 JS 对象字面量不同)。
  • JSON.parse(jsonString) 失败抛 SyntaxError(如少括号、单引号);读取 data01.json 等配置文件时必须 try/catch.catch
  • undefined、函数、Date 对象不能直接出现在 JSON 文本里;日期常以 ISO 字符串存储,解析后用 reviver 转回 Date
JSON 数据类型

#mermaid-svg-quBJ1nHQI1VGemwu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-quBJ1nHQI1VGemwu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-quBJ1nHQI1VGemwu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-quBJ1nHQI1VGemwu .error-icon{fill:#552222;}#mermaid-svg-quBJ1nHQI1VGemwu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-quBJ1nHQI1VGemwu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-quBJ1nHQI1VGemwu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-quBJ1nHQI1VGemwu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-quBJ1nHQI1VGemwu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-quBJ1nHQI1VGemwu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-quBJ1nHQI1VGemwu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-quBJ1nHQI1VGemwu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-quBJ1nHQI1VGemwu .marker.cross{stroke:#333333;}#mermaid-svg-quBJ1nHQI1VGemwu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-quBJ1nHQI1VGemwu p{margin:0;}#mermaid-svg-quBJ1nHQI1VGemwu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-quBJ1nHQI1VGemwu .cluster-label text{fill:#333;}#mermaid-svg-quBJ1nHQI1VGemwu .cluster-label span{color:#333;}#mermaid-svg-quBJ1nHQI1VGemwu .cluster-label span p{background-color:transparent;}#mermaid-svg-quBJ1nHQI1VGemwu .label text,#mermaid-svg-quBJ1nHQI1VGemwu span{fill:#333;color:#333;}#mermaid-svg-quBJ1nHQI1VGemwu .node rect,#mermaid-svg-quBJ1nHQI1VGemwu .node circle,#mermaid-svg-quBJ1nHQI1VGemwu .node ellipse,#mermaid-svg-quBJ1nHQI1VGemwu .node polygon,#mermaid-svg-quBJ1nHQI1VGemwu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-quBJ1nHQI1VGemwu .rough-node .label text,#mermaid-svg-quBJ1nHQI1VGemwu .node .label text,#mermaid-svg-quBJ1nHQI1VGemwu .image-shape .label,#mermaid-svg-quBJ1nHQI1VGemwu .icon-shape .label{text-anchor:middle;}#mermaid-svg-quBJ1nHQI1VGemwu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-quBJ1nHQI1VGemwu .rough-node .label,#mermaid-svg-quBJ1nHQI1VGemwu .node .label,#mermaid-svg-quBJ1nHQI1VGemwu .image-shape .label,#mermaid-svg-quBJ1nHQI1VGemwu .icon-shape .label{text-align:center;}#mermaid-svg-quBJ1nHQI1VGemwu .node.clickable{cursor:pointer;}#mermaid-svg-quBJ1nHQI1VGemwu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-quBJ1nHQI1VGemwu .arrowheadPath{fill:#333333;}#mermaid-svg-quBJ1nHQI1VGemwu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-quBJ1nHQI1VGemwu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-quBJ1nHQI1VGemwu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-quBJ1nHQI1VGemwu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-quBJ1nHQI1VGemwu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-quBJ1nHQI1VGemwu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-quBJ1nHQI1VGemwu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-quBJ1nHQI1VGemwu .cluster text{fill:#333;}#mermaid-svg-quBJ1nHQI1VGemwu .cluster span{color:#333;}#mermaid-svg-quBJ1nHQI1VGemwu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-quBJ1nHQI1VGemwu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-quBJ1nHQI1VGemwu rect.text{fill:none;stroke-width:0;}#mermaid-svg-quBJ1nHQI1VGemwu .icon-shape,#mermaid-svg-quBJ1nHQI1VGemwu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-quBJ1nHQI1VGemwu .icon-shape p,#mermaid-svg-quBJ1nHQI1VGemwu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-quBJ1nHQI1VGemwu .icon-shape .label rect,#mermaid-svg-quBJ1nHQI1VGemwu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-quBJ1nHQI1VGemwu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-quBJ1nHQI1VGemwu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-quBJ1nHQI1VGemwu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} JSON 数据类型
字符串 string
数字 number
布尔值 boolean
数组 array
对象 object
null

4.2 JSON 对象方法

JSON.stringify()
javascript 复制代码
// 【代码注释】基础序列化
const person = {
    name: 'Alice',
    age: 25,
    city: 'Boston',
    skills: ['JavaScript', 'Node.js', 'React']
};

const jsonString = JSON.stringify(person);
console.log('序列化结果:', jsonString);
// 输出: '{"name":"Alice","age":25,"city":"Boston","skills":["JavaScript","Node.js","React"]}'

// 【代码注释】格式化输出(带缩进)
const formattedJson = JSON.stringify(person, null, 2);
console.log('格式化的 JSON:');
console.log(formattedJson);
/*
输出:
{
  "name": "Alice",
  "age": 25,
  "city": "Boston",
  "skills": [
    "JavaScript",
    "Node.js",
    "React"
  ]
}
*/

// 【代码注释】使用 replacer 函数过滤属性
const sensitiveData = {
    username: 'user123',
    password: 'secret123',
    email: 'user@example.com',
    apiKey: 'abc123xyz'
};

const safeJson = JSON.stringify(sensitiveData, (key, value) => {
    // 【代码注释】过滤敏感信息
    if (key === 'password' || key === 'apiKey') {
        return undefined;
    }
    return value;
}, 2);

console.log('安全的 JSON:', safeJson);

// 【代码注释】序列化日期对象
const data = {
    timestamp: Date.now(),
    date: new Date(),
    customDate: new Date().toISOString()
};

const dataJson = JSON.stringify(data);
console.log('包含日期的 JSON:', dataJson);

// 【代码注释】处理循环引用
const obj = { name: 'Object' };
obj.self = obj; // 创建循环引用

try {
    // 【代码注释】直接序列化会报错
    // JSON.stringify(obj); // TypeError: Converting circular structure to JSON
    
    // 【代码注释】使用自定义 replacer 处理循环引用
    const safeStringify = (obj, indent = 2) => {
        const cache = new Set();
        return JSON.stringify(obj, (key, value) => {
            if (typeof value === 'object' && value !== null) {
                if (cache.has(value)) {
                    return '[Circular]';
                }
                cache.add(value);
            }
            return value;
        }, indent);
    };
    
    console.log('处理循环引用:', safeStringify(obj));
    
} catch (error) {
    console.error('序列化错误:', error.message);
}
JSON.parse()
javascript 复制代码
// 【代码注释】基础解析
const jsonStr = '{"name":"Bob","age":35,"isActive":true}';
const parsedObj = JSON.parse(jsonStr);
console.log('解析结果:', parsedObj);
console.log('姓名:', parsedObj.name);
console.log('年龄:', parsedObj.age);

// 【代码注释】使用 reviver 函数转换数据
const jsonData = `{
    "name": "Charlie",
    "birthDate": "1990-05-15",
    "score": "95",
    "price": "99.99"
}`;

const transformed = JSON.parse(jsonData, (key, value) => {
    // 【代码注释】转换特定字段
    if (key === 'birthDate') {
        return new Date(value);
    }
    if (key === 'score' || key === 'price') {
        return Number(value);
    }
    return value;
});

console.log('转换后的数据:', transformed);
console.log('生日:', transformed.birthDate instanceof Date); // true
console.log('分数:', typeof transformed.score); // 'number'

// 【代码注释】错误处理
function safeJsonParse(jsonString, defaultValue = null) {
    try {
        return JSON.parse(jsonString);
    } catch (error) {
        console.error('JSON 解析失败:', error.message);
        return defaultValue;
    }
}

const invalidJson = '{invalid json}';
const result = safeJsonParse(invalidJson, {});
console.log('解析结果:', result); // {}

// 【代码注释】解析数组
const jsonArray = '[{"id":1,"name":"Item 1"},{"id":2,"name":"Item 2"}]';
const items = JSON.parse(jsonArray);
console.log('解析的数组:', items);
items.forEach(item => {
    console.log(`- ${item.id}: ${item.name}`);
});

【代码注释】

  • JSON.stringify(obj, null, 2) 第三参数为缩进,生成可读的配置文件;生产接口常省略缩进以减小体积。
  • replacer 可为函数:返回 undefined 的键会被省略,用于过滤密码字段;数组形式的 replacer 白名单指定要序列化的键。
  • JSON.parse(text, reviver) 的 reviver 在解析每个键值时调用,可把 ISO 日期字符串转回 Date 对象。
  • 循环引用对象直接 stringify 会抛错,需自定义 replacer 或拆结构;safeJsonParse 模式避免坏配置拖垮启动流程。

4.3 JSON 实战应用

配置文件管理
javascript 复制代码
const fs = require('fs').promises;
const path = require('path');

// 【实战案例】配置文件管理器
class ConfigManager {
    constructor(configPath) {
        this.configPath = configPath;
        this.config = null;
    }
    
    // 【代码注释】加载配置文件
    async load() {
        try {
            const content = await fs.readFile(this.configPath, 'utf-8');
            this.config = JSON.parse(content);
            console.log('配置加载成功');
            return this.config;
        } catch (error) {
            if (error.code === 'ENOENT') {
                console.log('配置文件不存在,创建默认配置');
                return this.createDefaultConfig();
            } else if (error instanceof SyntaxError) {
                throw new Error('配置文件格式错误');
            } else {
                throw error;
            }
        }
    }
    
    // 【代码注释】创建默认配置
    async createDefaultConfig() {
        this.config = {
            app: {
                name: 'My Application',
                version: '1.0.0',
                port: 3000
            },
            database: {
                host: 'localhost',
                port: 5432,
                name: 'mydb',
                username: 'user',
                password: ''
            },
            logging: {
                level: 'info',
                file: 'app.log'
            }
        };
        
        await this.save();
        return this.config;
    }
    
    // 【代码注释】保存配置文件
    async save() {
        try {
            const formatted = JSON.stringify(this.config, null, 2);
            await fs.writeFile(this.configPath, formatted, 'utf-8');
            console.log('配置保存成功');
        } catch (error) {
            console.error('配置保存失败:', error.message);
            throw error;
        }
    }
    
    // 【代码注释】获取配置项
    get(key) {
        const keys = key.split('.');
        let value = this.config;
        
        for (const k of keys) {
            if (value && typeof value === 'object') {
                value = value[k];
            } else {
                return undefined;
            }
        }
        
        return value;
    }
    
    // 【代码注释】设置配置项
    set(key, value) {
        const keys = key.split('.');
        let current = this.config;
        
        for (let i = 0; i < keys.length - 1; i++) {
            const k = keys[i];
            if (!(k in current) || typeof current[k] !== 'object') {
                current[k] = {};
            }
            current = current[k];
        }
        
        current[keys[keys.length - 1]] = value;
    }
}

// 使用示例
async function testConfigManager() {
    const configPath = path.join(__dirname, 'config.json');
    const manager = new ConfigManager(configPath);
    
    // 加载或创建配置
    await manager.load();
    
    // 读取配置
    console.log('应用名称:', manager.get('app.name'));
    console.log('数据库端口:', manager.get('database.port'));
    
    // 修改配置
    manager.set('app.port', 8080);
    manager.set('database.password', 'secret');
    
    // 保存配置
    await manager.save();
}

testConfigManager();

【代码注释】

  • load()ENOENT 时走 createDefaultConfig() 写盘,适合首次部署;SyntaxError 表示 JSON 损坏,应告警而非静默覆盖。
  • get('app.name') 用点分路径访问嵌套对象,与 lodash.get 思路相同;中间缺失键返回 undefined
  • set 自动创建中间对象,便于 manager.set('database.port', 3306)save()stringify 写回,注意并发写需加锁或单进程。
  • 课堂 data01.json 读写与该类相同,可对照 §4 的 parse/stringify 练习。

5. 模块化规范深度解析

5.0 主流模块化规范对照

规范 环境 加载方式 关键字 现状
CommonJS Node、Webpack 打包 同步(运行时) require / module.exports Node 默认,生态最广
AMD 浏览器(RequireJS) 异步 define 已少见
CMD 浏览器(SeaJS) 异步,偏 CMD 写法 define 已少见
ES Module 浏览器原生 + Node 静态分析 import / export 官方推荐方向

#mermaid-svg-sxBR5XrkH0OBJN26{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sxBR5XrkH0OBJN26 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sxBR5XrkH0OBJN26 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sxBR5XrkH0OBJN26 .error-icon{fill:#552222;}#mermaid-svg-sxBR5XrkH0OBJN26 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sxBR5XrkH0OBJN26 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sxBR5XrkH0OBJN26 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sxBR5XrkH0OBJN26 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sxBR5XrkH0OBJN26 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sxBR5XrkH0OBJN26 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sxBR5XrkH0OBJN26 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sxBR5XrkH0OBJN26 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sxBR5XrkH0OBJN26 .marker.cross{stroke:#333333;}#mermaid-svg-sxBR5XrkH0OBJN26 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sxBR5XrkH0OBJN26 p{margin:0;}#mermaid-svg-sxBR5XrkH0OBJN26 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sxBR5XrkH0OBJN26 .cluster-label text{fill:#333;}#mermaid-svg-sxBR5XrkH0OBJN26 .cluster-label span{color:#333;}#mermaid-svg-sxBR5XrkH0OBJN26 .cluster-label span p{background-color:transparent;}#mermaid-svg-sxBR5XrkH0OBJN26 .label text,#mermaid-svg-sxBR5XrkH0OBJN26 span{fill:#333;color:#333;}#mermaid-svg-sxBR5XrkH0OBJN26 .node rect,#mermaid-svg-sxBR5XrkH0OBJN26 .node circle,#mermaid-svg-sxBR5XrkH0OBJN26 .node ellipse,#mermaid-svg-sxBR5XrkH0OBJN26 .node polygon,#mermaid-svg-sxBR5XrkH0OBJN26 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sxBR5XrkH0OBJN26 .rough-node .label text,#mermaid-svg-sxBR5XrkH0OBJN26 .node .label text,#mermaid-svg-sxBR5XrkH0OBJN26 .image-shape .label,#mermaid-svg-sxBR5XrkH0OBJN26 .icon-shape .label{text-anchor:middle;}#mermaid-svg-sxBR5XrkH0OBJN26 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sxBR5XrkH0OBJN26 .rough-node .label,#mermaid-svg-sxBR5XrkH0OBJN26 .node .label,#mermaid-svg-sxBR5XrkH0OBJN26 .image-shape .label,#mermaid-svg-sxBR5XrkH0OBJN26 .icon-shape .label{text-align:center;}#mermaid-svg-sxBR5XrkH0OBJN26 .node.clickable{cursor:pointer;}#mermaid-svg-sxBR5XrkH0OBJN26 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sxBR5XrkH0OBJN26 .arrowheadPath{fill:#333333;}#mermaid-svg-sxBR5XrkH0OBJN26 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sxBR5XrkH0OBJN26 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sxBR5XrkH0OBJN26 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sxBR5XrkH0OBJN26 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sxBR5XrkH0OBJN26 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sxBR5XrkH0OBJN26 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sxBR5XrkH0OBJN26 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sxBR5XrkH0OBJN26 .cluster text{fill:#333;}#mermaid-svg-sxBR5XrkH0OBJN26 .cluster span{color:#333;}#mermaid-svg-sxBR5XrkH0OBJN26 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sxBR5XrkH0OBJN26 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sxBR5XrkH0OBJN26 rect.text{fill:none;stroke-width:0;}#mermaid-svg-sxBR5XrkH0OBJN26 .icon-shape,#mermaid-svg-sxBR5XrkH0OBJN26 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sxBR5XrkH0OBJN26 .icon-shape p,#mermaid-svg-sxBR5XrkH0OBJN26 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sxBR5XrkH0OBJN26 .icon-shape .label rect,#mermaid-svg-sxBR5XrkH0OBJN26 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sxBR5XrkH0OBJN26 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sxBR5XrkH0OBJN26 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sxBR5XrkH0OBJN26 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 历史
CommonJS

Node 默认
Webpack 转换
ES Module

标准
Vite 原生 ESM
AMD
老项目

【代码注释】

  • CommonJSrequire / module.exports,Node 默认,同步加载,适合服务端与课堂 01-mod08-mod 案例。
  • ESMimport / export,浏览器同源语法;Node 通过 .mjspackage.json"type":"module" 启用。
  • 同一 .js 文件内不要混写 requireimport(除非使用实验性互操作或拆成两个入口);第三方包需看其 exports 字段支持哪种格式。
  • AMD/CMD 多见于历史前端(RequireJS、SeaJS),现代工程由 Webpack/Vite 在构建期打包,运行时仍是 CJS 或 ESM。

5.1 CommonJS 规范

CommonJS 是 Node.js 默认使用的模块化规范,采用同步加载方式,主要服务于服务端环境。

CommonJS 核心概念

#mermaid-svg-yFqAJRNB8RPvRKDj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yFqAJRNB8RPvRKDj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yFqAJRNB8RPvRKDj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yFqAJRNB8RPvRKDj .error-icon{fill:#552222;}#mermaid-svg-yFqAJRNB8RPvRKDj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yFqAJRNB8RPvRKDj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yFqAJRNB8RPvRKDj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yFqAJRNB8RPvRKDj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yFqAJRNB8RPvRKDj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yFqAJRNB8RPvRKDj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yFqAJRNB8RPvRKDj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yFqAJRNB8RPvRKDj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yFqAJRNB8RPvRKDj .marker.cross{stroke:#333333;}#mermaid-svg-yFqAJRNB8RPvRKDj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yFqAJRNB8RPvRKDj p{margin:0;}#mermaid-svg-yFqAJRNB8RPvRKDj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yFqAJRNB8RPvRKDj .cluster-label text{fill:#333;}#mermaid-svg-yFqAJRNB8RPvRKDj .cluster-label span{color:#333;}#mermaid-svg-yFqAJRNB8RPvRKDj .cluster-label span p{background-color:transparent;}#mermaid-svg-yFqAJRNB8RPvRKDj .label text,#mermaid-svg-yFqAJRNB8RPvRKDj span{fill:#333;color:#333;}#mermaid-svg-yFqAJRNB8RPvRKDj .node rect,#mermaid-svg-yFqAJRNB8RPvRKDj .node circle,#mermaid-svg-yFqAJRNB8RPvRKDj .node ellipse,#mermaid-svg-yFqAJRNB8RPvRKDj .node polygon,#mermaid-svg-yFqAJRNB8RPvRKDj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yFqAJRNB8RPvRKDj .rough-node .label text,#mermaid-svg-yFqAJRNB8RPvRKDj .node .label text,#mermaid-svg-yFqAJRNB8RPvRKDj .image-shape .label,#mermaid-svg-yFqAJRNB8RPvRKDj .icon-shape .label{text-anchor:middle;}#mermaid-svg-yFqAJRNB8RPvRKDj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yFqAJRNB8RPvRKDj .rough-node .label,#mermaid-svg-yFqAJRNB8RPvRKDj .node .label,#mermaid-svg-yFqAJRNB8RPvRKDj .image-shape .label,#mermaid-svg-yFqAJRNB8RPvRKDj .icon-shape .label{text-align:center;}#mermaid-svg-yFqAJRNB8RPvRKDj .node.clickable{cursor:pointer;}#mermaid-svg-yFqAJRNB8RPvRKDj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yFqAJRNB8RPvRKDj .arrowheadPath{fill:#333333;}#mermaid-svg-yFqAJRNB8RPvRKDj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yFqAJRNB8RPvRKDj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yFqAJRNB8RPvRKDj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yFqAJRNB8RPvRKDj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yFqAJRNB8RPvRKDj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yFqAJRNB8RPvRKDj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yFqAJRNB8RPvRKDj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yFqAJRNB8RPvRKDj .cluster text{fill:#333;}#mermaid-svg-yFqAJRNB8RPvRKDj .cluster span{color:#333;}#mermaid-svg-yFqAJRNB8RPvRKDj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yFqAJRNB8RPvRKDj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yFqAJRNB8RPvRKDj rect.text{fill:none;stroke-width:0;}#mermaid-svg-yFqAJRNB8RPvRKDj .icon-shape,#mermaid-svg-yFqAJRNB8RPvRKDj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yFqAJRNB8RPvRKDj .icon-shape p,#mermaid-svg-yFqAJRNB8RPvRKDj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yFqAJRNB8RPvRKDj .icon-shape .label rect,#mermaid-svg-yFqAJRNB8RPvRKDj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yFqAJRNB8RPvRKDj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yFqAJRNB8RPvRKDj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yFqAJRNB8RPvRKDj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CommonJS 模块系统
require 导入模块
module.exports 导出模块
__dirname 当前目录
__filename 当前文件路径
导出单个值
导出多个值
动态导出
相对路径导入
绝对路径导入
内置模块导入

模块导出方式
javascript 复制代码
// 【方式1】导出空对象(默认情况)
// 文件: modules/01-mod.js
// 如果没有显式导出,require() 返回空对象 {}

// 【方式2】使用 module.exports 导出单个值
// 文件: modules/02-mod.js
const greeting = 'Hello, World!';
function sayHello() {
    console.log(greeting);
}

// 【代码注释】导出一个包含多个方法的对象
module.exports = {
    greeting,
    sayHello,
    name: 'CommonJS Module',
    version: '1.0.0'
};

// 【方式3】使用 module.exports 添加属性
// 文件: modules/03-mod.js
module.exports.getMessage = function() {
    return 'This is a message from module';
};

module.exports.config = {
    debug: true,
    version: '1.0.0'
};

module.exports.util = {
    format: function(str) {
        return str.toUpperCase();
    }
};

// 【方式4】使用 exports 别名(注意限制)
// 文件: modules/04-mod.js
// 【代码注释】exports 是 module.exports 的引用,只能添加属性,不能重新赋值
exports.name = 'Module 4';
exports.version = '2.0.0';
exports.getData = function() {
    return { id: 1, value: 'test' };
};

// 【代码注释】错误示例:不要这样使用
// exports = { name: 'Error' }; // 这样会断开与 module.exports 的连接

// 【方式5】导出 JSON 数据
// 文件: modules/05-mod.json
{
    "name": "JSON Module",
    "version": "1.0.0",
    "description": "A JSON configuration file"
}

// 【方式6】导出函数或类
// 文件: modules/06-mod.js
class UserService {
    constructor() {
        this.users = [];
    }
    
    addUser(user) {
        this.users.push(user);
        console.log(`用户 ${user.name} 已添加`);
    }
    
    getUsers() {
        return this.users;
    }
}

// 【代码注释】导出构造函数
module.exports = UserService;

// 【方式7】条件导出
// 文件: modules/07-mod.js
function getData() {
    return process.env.NODE_ENV === 'production' 
        ? { mode: 'production' } 
        : { mode: 'development' };
}

module.exports = getData();
模块导入方式
javascript 复制代码
// 【代码注释】主文件:index.js
const path = require('path');

// 【导入1】基本导入
const mod01 = require('./modules/01-mod.js');
console.log('模块1:', mod01); // {}

// 【导入2】导入并解构
const mod02 = require('./modules/02-mod');
const { sayHello, name } = mod02;
sayHello();
console.log('模块名称:', name);

// 【导入3】直接解构导入
const { getMessage, config } = require('./modules/03-mod');
console.log('消息:', getMessage());
console.log('配置:', config);

// 【导入4】导入 JSON 文件
const jsonConfig = require('./modules/05-mod.json');
console.log('JSON 配置:', jsonConfig);

// 【导入5】导入类/构造函数
const UserService = require('./modules/06-mod');
const userService = new UserService();
userService.addUser({ name: 'Alice', age: 30 });
console.log('用户列表:', userService.getUsers());

// 【导入6】导入内置模块
const fs = require('fs');
const http = require('http');

// 【导入7】路径处理
const customModule = require(path.join(__dirname, 'modules', 'custom.js'));

// 【导入8】模块缓存验证
console.log('\n模块缓存测试:');
require('./modules/02-mod'); // 第一次执行
require('./modules/02-mod'); // 使用缓存
require('./modules/02-mod'); // 使用缓存
console.log('模块只加载一次,后续使用缓存');
模块加载机制
javascript 复制代码
// 【代码注释】模块查找路径解析
console.log('模块查找路径:');
console.log('1. 相对路径模块: ./modules/custom.js');
console.log('2. 绝对路径模块: /path/to/module.js');
console.log('3. 内置模块: fs, path, http');
console.log('4. node_modules 中的模块');

// 【代码注释】查看模块查找路径
console.log('\n模块查找路径 (module.paths):');
module.paths.forEach((path, index) => {
    console.log(`${index + 1}. ${path}`);
});

// 【代码注释】模块信息
console.log('\n当前模块信息:');
console.log('文件名:', __filename);
console.log('目录名:', __dirname);
console.log('模块ID:', module.id);
console.log('是否为主模块:', require.main === module);

// 【代码注释】动态模块加载
function dynamicRequire(moduleName) {
    try {
        const module = require(moduleName);
        console.log(`模块 ${moduleName} 加载成功`);
        return module;
    } catch (error) {
        console.error(`模块 ${moduleName} 加载失败:`, error.message);
        return null;
    }
}

// 使用动态加载
const fsExtra = dynamicRequire('fs-extra'); // 如果未安装会返回 null
const pathModule = dynamicRequire('path'); // 内置模块,加载成功
CommonJS 实战应用
javascript 复制代码
// 【实战案例】工具函数模块
// 文件: utils/logger.js
const fs = require('fs');
const path = require('path');

class Logger {
    constructor(logDir = './logs') {
        this.logDir = logDir;
        this.ensureLogDirectory();
    }
    
    ensureLogDirectory() {
        if (!fs.existsSync(this.logDir)) {
            fs.mkdirSync(this.logDir, { recursive: true });
        }
    }
    
    log(level, message) {
        const timestamp = new Date().toISOString();
        const logMessage = `[${timestamp}] [${level}] ${message}\n`;
        
        console.log(logMessage.trim());
        
        const logFile = path.join(this.logDir, 'app.log');
        fs.appendFileSync(logFile, logMessage);
    }
    
    info(message) {
        this.log('INFO', message);
    }
    
    error(message) {
        this.log('ERROR', message);
    }
    
    warn(message) {
        this.log('WARN', message);
    }
}

// 【代码注释】导出单例
module.exports = new Logger();

// 【实战案例】使用日志模块
// 文件: app.js
const logger = require('./utils/logger');

logger.info('应用程序启动');
logger.warn('配置文件缺失,使用默认配置');
logger.error('数据库连接失败');

// 【实战案例】配置管理模块
// 文件: config/database.js
module.exports = {
    development: {
        host: 'localhost',
        port: 5432,
        database: 'dev_db',
        username: 'dev_user',
        password: 'dev_pass'
    },
    
    production: {
        host: process.env.DB_HOST || 'prod-db.example.com',
        port: parseInt(process.env.DB_PORT) || 5432,
        database: process.env.DB_NAME || 'prod_db',
        username: process.env.DB_USER || 'prod_user',
        password: process.env.DB_PASSWORD || ''
    },
    
    test: {
        host: 'localhost',
        port: 5432,
        database: 'test_db',
        username: 'test_user',
        password: 'test_pass'
    }
};

// 【代码注释】根据环境获取配置
// 文件: config/index.js
const dbConfig = require('./database');

function getConfig() {
    const env = process.env.NODE_ENV || 'development';
    return dbConfig[env] || dbConfig.development;
}

module.exports = { getConfig };

5.2 ES6 模块规范

ES6 模块(ESM)是 JavaScript 官方的模块化标准,采用静态导入/导出,支持 tree-shaking,是现代前端开发的主流选择。

ES6 vs CommonJS 对比

#mermaid-svg-WcfxCtXWkphkXamW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WcfxCtXWkphkXamW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WcfxCtXWkphkXamW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WcfxCtXWkphkXamW .error-icon{fill:#552222;}#mermaid-svg-WcfxCtXWkphkXamW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WcfxCtXWkphkXamW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WcfxCtXWkphkXamW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WcfxCtXWkphkXamW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WcfxCtXWkphkXamW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WcfxCtXWkphkXamW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WcfxCtXWkphkXamW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WcfxCtXWkphkXamW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WcfxCtXWkphkXamW .marker.cross{stroke:#333333;}#mermaid-svg-WcfxCtXWkphkXamW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WcfxCtXWkphkXamW p{margin:0;}#mermaid-svg-WcfxCtXWkphkXamW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WcfxCtXWkphkXamW .cluster-label text{fill:#333;}#mermaid-svg-WcfxCtXWkphkXamW .cluster-label span{color:#333;}#mermaid-svg-WcfxCtXWkphkXamW .cluster-label span p{background-color:transparent;}#mermaid-svg-WcfxCtXWkphkXamW .label text,#mermaid-svg-WcfxCtXWkphkXamW span{fill:#333;color:#333;}#mermaid-svg-WcfxCtXWkphkXamW .node rect,#mermaid-svg-WcfxCtXWkphkXamW .node circle,#mermaid-svg-WcfxCtXWkphkXamW .node ellipse,#mermaid-svg-WcfxCtXWkphkXamW .node polygon,#mermaid-svg-WcfxCtXWkphkXamW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WcfxCtXWkphkXamW .rough-node .label text,#mermaid-svg-WcfxCtXWkphkXamW .node .label text,#mermaid-svg-WcfxCtXWkphkXamW .image-shape .label,#mermaid-svg-WcfxCtXWkphkXamW .icon-shape .label{text-anchor:middle;}#mermaid-svg-WcfxCtXWkphkXamW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WcfxCtXWkphkXamW .rough-node .label,#mermaid-svg-WcfxCtXWkphkXamW .node .label,#mermaid-svg-WcfxCtXWkphkXamW .image-shape .label,#mermaid-svg-WcfxCtXWkphkXamW .icon-shape .label{text-align:center;}#mermaid-svg-WcfxCtXWkphkXamW .node.clickable{cursor:pointer;}#mermaid-svg-WcfxCtXWkphkXamW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WcfxCtXWkphkXamW .arrowheadPath{fill:#333333;}#mermaid-svg-WcfxCtXWkphkXamW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WcfxCtXWkphkXamW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WcfxCtXWkphkXamW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WcfxCtXWkphkXamW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WcfxCtXWkphkXamW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WcfxCtXWkphkXamW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WcfxCtXWkphkXamW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WcfxCtXWkphkXamW .cluster text{fill:#333;}#mermaid-svg-WcfxCtXWkphkXamW .cluster span{color:#333;}#mermaid-svg-WcfxCtXWkphkXamW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WcfxCtXWkphkXamW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WcfxCtXWkphkXamW rect.text{fill:none;stroke-width:0;}#mermaid-svg-WcfxCtXWkphkXamW .icon-shape,#mermaid-svg-WcfxCtXWkphkXamW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WcfxCtXWkphkXamW .icon-shape p,#mermaid-svg-WcfxCtXWkphkXamW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WcfxCtXWkphkXamW .icon-shape .label rect,#mermaid-svg-WcfxCtXWkphkXamW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WcfxCtXWkphkXamW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WcfxCtXWkphkXamW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WcfxCtXWkphkXamW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 模块化规范选择
CommonJS
ES6 Modules
Node.js 原生支持
同步加载
动态导入
现代标准
静态分析
Tree-shaking 支持

ES6 模块导出语法
javascript 复制代码
// 【方式1】默认导出 (export default)
// 文件: esm-modules/default-export.mjs
const defaultMessage = 'This is the default export';

function defaultFunction() {
    console.log('This is a default function');
}

class DefaultClass {
    constructor() {
        this.name = 'Default Class';
    }
    
    greet() {
        console.log(`Hello from ${this.name}`);
    }
}

// 【代码注释】默认导出,一个模块只能有一个默认导出
export default defaultMessage;
// 或导出函数
// export default defaultFunction;
// 或导出类
// export default DefaultClass;

// 【方式2】命名导出 (export)
// 文件: esm-modules/named-export.mjs
// 【代码注释】导出变量
export const PI = 3.14159;
export const appName = 'ES6 Module Demo';

// 【代码注释】导出函数
export function calculateArea(radius) {
    return PI * radius * radius;
}

export function calculateCircumference(radius) {
    return 2 * PI * radius;
}

// 【代码注释】导出类
export class Circle {
    constructor(radius) {
        this.radius = radius;
    }
    
    get area() {
        return calculateArea(this.radius);
    }
    
    get circumference() {
        return calculateCircumference(this.radius);
    }
}

// 【方式3】混合导出
// 文件: esm-modules/mixed-export.mjs
// 【代码注释】默认导出和命名导出可以共存
export default function main() {
    console.log('这是主要功能');
}

export const version = '1.0.0';
export const author = 'Developer';

export function helperFunction() {
    console.log('这是辅助功能');
}

// 【方式4】导出列表(推荐)
// 文件: esm-modules/export-list.mjs
// 【代码注释】先声明,后导出,结构更清晰
const config = {
    debug: true,
    version: '2.0.0'
};

function init() {
    console.log('初始化应用');
}

function cleanup() {
    console.log('清理资源');
}

class Application {
    constructor() {
        this.config = config;
    }
    
    start() {
        init();
    }
    
    stop() {
        cleanup();
    }
}

// 【代码注释】统一导出声明
export { config, init, cleanup, Application };

// 【方式5】导出时重命名
export { config as appConfig, Application as App };

// 【方式6】重新导出其他模块
// 文件: esm-modules/re-export.mjs
// 【代码注释】重新导出其他模块的内容
export { default } from './default-export.mjs';
export { PI, calculateArea, Circle } from './named-export.mjs';
// 【代码注释】重新导出并重命名
export { calculateCircumference as getCircumference } from './named-export.mjs';
ES6 模块导入语法
javascript 复制代码
// 【方式1】导入默认导出
// 文件: esm-modules/import-default.mjs
import defaultMessage from './default-export.mjs';
import defaultFunction from './default-export.mjs';

console.log('默认导入:', defaultMessage);
defaultFunction();

// 【方式2】导入命名导出
// 文件: esm-modules/import-named.mjs
import { PI, appName, calculateArea } from './named-export.mjs';
import { Circle as CircleShape } from './named-export.mjs'; // 导入时重命名

console.log('圆周率:', PI);
console.log('应用名称:', appName);
console.log('面积计算:', calculateArea(5));

const circle = new CircleShape(10);
console.log('圆的面积:', circle.area);

// 【方式3】导入全部命名导出
// 文件: esm-modules/import-all.mjs
import * as mathUtils from './named-export.mjs';

console.log('所有数学工具:', mathUtils);
console.log('使用导入的工具:', mathUtils.calculateArea(3));

// 【方式4】混合导入
// 文件: esm-modules/import-mixed.mjs
import mainFunction, { version, author, helperFunction } from './mixed-export.mjs';

mainFunction();
console.log('版本:', version, '作者:', author);
helperFunction();

// 【方式5】只导入副作用
// 文件: esm-modules/import-side-effect.mjs
// 【代码注释】只执行模块代码,不导入任何内容
import './polyfills.mjs';

// 【方式6】动态导入
// 文件: esm-modules/dynamic-import.mjs
async function loadModule() {
    try {
        // 【代码注释】动态导入返回 Promise
        const module = await import('./named-export.mjs');
        console.log('动态导入成功:', module);
        
        const result = module.calculateArea(10);
        console.log('计算结果:', result);
    } catch (error) {
        console.error('模块加载失败:', error.message);
    }
}

loadModule();
在 Node.js 中使用 ES6 模块
javascript 复制代码
// 【方式1】使用 .mjs 扩展名
// 文件: app.mjs
import { readFile } from 'fs/promises';
import path from 'path';

async function main() {
    const filePath = path.join(process.cwd(), 'data.txt');
    const content = await readFile(filePath, 'utf-8');
    console.log('文件内容:', content);
}

main();

// 【方式2】在 package.json 中设置 "type": "module"
// 文件: package.json
/*
{
  "name": "es6-module-demo",
  "version": "1.0.0",
  "type": "module",
  "main": "app.js"
}
*/

// 【方式3】使用 .js 扩展名但配置为 ESM
// 文件: app.js (在 "type": "module" 项目中)
import express from 'express';
import { router } from './routes/index.js';

const app = express();
app.use(router);

app.listen(3000, () => {
    console.log('服务器运行在 http://localhost:3000');
});
ES6 模块实战项目
javascript 复制代码
// 【实战案例】工具函数库
// 文件: utils/string-utils.mjs
// 【代码注释】字符串处理工具函数

export function capitalize(str) {
    if (!str) return '';
    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}

export function truncate(str, length = 50) {
    if (!str || str.length <= length) return str;
    return str.substring(0, length) + '...';
}

export function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}

export function generateRandomString(length = 10) {
    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let result = '';
    for (let i = 0; i < length; i++) {
        result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
}

// 【实战案例】日期工具函数
// 文件: utils/date-utils.mjs
export function formatDate(date, format = 'YYYY-MM-DD') {
    const d = new Date(date);
    const year = d.getFullYear();
    const month = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    const hours = String(d.getHours()).padStart(2, '0');
    const minutes = String(d.getMinutes()).padStart(2, '0');
    const seconds = String(d.getSeconds()).padStart(2, '0');
    
    return format
        .replace('YYYY', year)
        .replace('MM', month)
        .replace('DD', day)
        .replace('HH', hours)
        .replace('mm', minutes)
        .replace('ss', seconds);
}

export function addDays(date, days) {
    const result = new Date(date);
    result.setDate(result.getDate() + days);
    return result;
}

export function isWeekend(date) {
    const day = new Date(date).getDay();
    return day === 0 || day === 6;
}

// 【实战案例】主应用文件
// 文件: app.mjs
import { capitalize, slugify, generateRandomString } from './utils/string-utils.mjs';
import { formatDate, addDays, isWeekend } from './utils/date-utils.mjs';

class BlogPostGenerator {
    constructor(title) {
        this.title = title;
        this.slug = slugify(title);
        this.createdAt = new Date();
        this.id = generateRandomString(8);
    }
    
    generate() {
        return {
            id: this.id,
            title: capitalize(this.title),
            slug: this.slug,
            createdAt: formatDate(this.createdAt, 'YYYY-MM-DD HH:mm:ss'),
            publishDate: formatDate(addDays(this.createdAt, 7), 'YYYY-MM-DD'),
            isWeekendPublish: isWeekend(addDays(this.createdAt, 7))
        };
    }
}

// 【代码注释】使用示例
const post = new BlogPostGenerator('Introduction to ES6 Modules');
console.log('生成的博客文章:', post.generate());

// 【实战案例】API 客户端
// 文件: services/api-client.mjs
class APIClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.headers = {
            'Content-Type': 'application/json'
        };
    }
    
    async get(endpoint) {
        const response = await fetch(`${this.baseURL}${endpoint}`, {
            method: 'GET',
            headers: this.headers
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        return response.json();
    }
    
    async post(endpoint, data) {
        const response = await fetch(`${this.baseURL}${endpoint}`, {
            method: 'POST',
            headers: this.headers,
            body: JSON.stringify(data)
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        return response.json();
    }
}

// 【代码注释】导出 API 客户端实例
export const apiClient = new APIClient('https://api.example.com');
export { APIClient };

6. 实战应用场景

6.1 文件服务器实现

javascript 复制代码
// 【实战案例】静态文件服务器
const http = require('http');
const fs = require('fs');
const path = require('path');

class StaticFileServer {
    constructor(rootDir, port = 3000) {
        this.rootDir = path.resolve(rootDir);
        this.port = port;
        this.mimeTypes = {
            '.html': 'text/html',
            '.css': 'text/css',
            '.js': 'application/javascript',
            '.json': 'application/json',
            '.png': 'image/png',
            '.jpg': 'image/jpeg',
            '.gif': 'image/gif',
            '.svg': 'image/svg+xml',
            '.ico': 'image/x-icon'
        };
    }
    
    getMimeType(filePath) {
        const ext = path.extname(filePath).toLowerCase();
        return this.mimeTypes[ext] || 'application/octet-stream';
    }
    
    serveFile(req, res) {
        // 【代码注释】构建文件路径,防止路径遍历攻击
        const requestedPath = path.normalize(req.url === '/' ? '/index.html' : req.url);
        const filePath = path.join(this.rootDir, requestedPath);
        
        // 【代码注释】安全检查:确保文件在根目录内
        if (!filePath.startsWith(this.rootDir)) {
            this.sendError(res, 403, '禁止访问');
            return;
        }
        
        // 【代码注释】检查文件是否存在
        fs.access(filePath, fs.constants.F_OK, (err) => {
            if (err) {
                this.sendError(res, 404, '文件不存在');
                return;
            }
            
            // 【代码注释】获取文件状态
            fs.stat(filePath, (err, stats) => {
                if (err) {
                    this.sendError(res, 500, '服务器错误');
                    return;
                }
                
                if (stats.isDirectory()) {
                    // 【代码注释】如果是目录,尝试提供 index.html
                    const indexPath = path.join(filePath, 'index.html');
                    fs.access(indexPath, fs.constants.F_OK, (err) => {
                        if (err) {
                            this.sendError(res, 404, '找不到索引文件');
                        } else {
                            this.sendFile(indexPath, res);
                        }
                    });
                } else {
                    // 【代码注释】发送文件
                    this.sendFile(filePath, res);
                }
            });
        });
    }
    
    sendFile(filePath, res) {
        const stream = fs.createReadStream(filePath);
        const mimeType = this.getMimeType(filePath);
        
        res.writeHead(200, {
            'Content-Type': mimeType,
            'Cache-Control': 'max-age=86400' // 缓存一天
        });
        
        stream.pipe(res);
        
        stream.on('error', (err) => {
            this.sendError(res, 500, '文件读取错误');
        });
    }
    
    sendError(res, statusCode, message) {
        res.writeHead(statusCode, { 'Content-Type': 'text/plain; charset=utf-8' });
        res.end(message);
    }
    
    start() {
        const server = http.createServer((req, res) => {
            this.serveFile(req, res);
        });
        
        server.listen(this.port, () => {
            console.log(`文件服务器启动在 http://localhost:${this.port}`);
            console.log(`根目录: ${this.rootDir}`);
        });
    }
}

// 使用示例
const server = new StaticFileServer('./public', 8080);
server.start();

6.2 日志系统实现

javascript 复制代码
// 【实战案例】高级日志系统
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');

class Logger {
    constructor(options = {}) {
        this.logDir = options.logDir || './logs';
        this.logFile = options.logFile || 'app.log';
        this.level = options.level || 'info';
        this.maxSize = options.maxSize || 10 * 1024 * 1024; // 10MB
        this.maxFiles = options.maxFiles || 5;
        
        this.levels = {
            error: 0,
            warn: 1,
            info: 2,
            debug: 3
        };
        
        this.init();
    }
    
    init() {
        // 【代码注释】确保日志目录存在
        if (!fs.existsSync(this.logDir)) {
            fs.mkdirSync(this.logDir, { recursive: true });
        }
    }
    
    shouldLog(level) {
        return this.levels[level] <= this.levels[this.level];
    }
    
    formatMessage(level, message, meta = {}) {
        const timestamp = new Date().toISOString();
        const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
        return `[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}\n`;
    }
    
    async writeLog(message) {
        const logPath = path.join(this.logDir, this.logFile);
        
        try {
            // 【代码注释】检查文件大小,如果超过限制则轮换
            if (fs.existsSync(logPath)) {
                const stats = fs.statSync(logPath);
                if (stats.size >= this.maxSize) {
                    await this.rotateLogs();
                }
            }
            
            // 【代码注释】追加日志
            await fs.promises.appendFile(logPath, message);
        } catch (error) {
            console.error('日志写入失败:', error.message);
        }
    }
    
    async rotateLogs() {
        const logPath = path.join(this.logDir, this.logFile);
        
        // 【代码注释】删除最老的日志文件
        const oldestLog = path.join(this.logDir, `${this.logFile}.${this.maxFiles}`);
        if (fs.existsSync(oldestLog)) {
            fs.unlinkSync(oldestLog);
        }
        
        // 【代码注释】重命名现有日志文件
        for (let i = this.maxFiles - 1; i >= 1; i--) {
            const oldLog = path.join(this.logDir, `${this.logFile}.${i}`);
            const newLog = path.join(this.logDir, `${this.logFile}.${i + 1}`);
            
            if (fs.existsSync(oldLog)) {
                fs.renameSync(oldLog, newLog);
            }
        }
        
        // 【代码注释】重命名当前日志文件
        const rotatedLog = path.join(this.logDir, `${this.logFile}.1`);
        if (fs.existsSync(logPath)) {
            fs.renameSync(logPath, rotatedLog);
        }
    }
    
    log(level, message, meta) {
        if (!this.shouldLog(level)) {
            return;
        }
        
        const formattedMessage = this.formatMessage(level, message, meta);
        
        // 【代码注释】输出到控制台
        console.log(formattedMessage.trim());
        
        // 【代码注释】写入文件
        this.writeLog(formattedMessage);
    }
    
    error(message, meta) {
        this.log('error', message, meta);
    }
    
    warn(message, meta) {
        this.log('warn', message, meta);
    }
    
    info(message, meta) {
        this.log('info', message, meta);
    }
    
    debug(message, meta) {
        this.log('debug', message, meta);
    }
}

// 使用示例
const logger = new Logger({
    logDir: './logs',
    logFile: 'app.log',
    level: 'debug',
    maxSize: 1024 * 1024, // 1MB
    maxFiles: 3
});

logger.info('应用程序启动');
logger.debug('调试信息', { userId: 123, action: 'login' });
logger.warn('警告信息', { memory: '80%' });
logger.error('错误信息', { error: 'Connection timeout', code: 'ETIMEDOUT' });

6.3 配置管理系统

javascript 复制代码
// 【实战案例】企业级配置管理
const fs = require('fs');
const path = require('path');

class ConfigManager {
    constructor(configDir = './config') {
        this.configDir = configDir;
        this.config = {};
        this.watchers = [];
    }
    
    load() {
        // 【代码注释】加载环境配置
        const env = process.env.NODE_ENV || 'development';
        const configFile = path.join(this.configDir, `${env}.json`);
        
        try {
            if (fs.existsSync(configFile)) {
                const content = fs.readFileSync(configFile, 'utf-8');
                this.config = JSON.parse(content);
                console.log(`配置加载成功: ${env}`);
            } else {
                console.warn(`配置文件不存在: ${configFile}`);
                this.config = this.getDefaultConfig();
            }
        } catch (error) {
            console.error('配置加载失败:', error.message);
            this.config = this.getDefaultConfig();
        }
        
        return this.config;
    }
    
    getDefaultConfig() {
        return {
            app: {
                name: 'Application',
                version: '1.0.0',
                port: 3000
            },
            database: {
                host: 'localhost',
                port: 5432,
                name: 'mydb'
            },
            logging: {
                level: 'info'
            }
        };
    }
    
    get(key, defaultValue = null) {
        const keys = key.split('.');
        let value = this.config;
        
        for (const k of keys) {
            if (value && typeof value === 'object' && k in value) {
                value = value[k];
            } else {
                return defaultValue;
            }
        }
        
        return value;
    }
    
    set(key, value) {
        const keys = key.split('.');
        let current = this.config;
        
        for (let i = 0; i < keys.length - 1; i++) {
            const k = keys[i];
            if (!(k in current) || typeof current[k] !== 'object') {
                current[k] = {};
            }
            current = current[k];
        }
        
        current[keys[keys.length - 1]] = value;
    }
    
    watch(key, callback) {
        // 【代码注释】监听配置变化
        this.watchers.push({ key, callback });
    }
    
    notify(key, value) {
        // 【代码注释】通知监听器
        this.watchers.forEach(({ key: watchedKey, callback }) => {
            if (key.startsWith(watchedKey)) {
                callback(key, value);
            }
        });
    }
    
    save(env = 'development') {
        const configFile = path.join(this.configDir, `${env}.json`);
        const content = JSON.stringify(this.config, null, 2);
        
        try {
            fs.writeFileSync(configFile, content, 'utf-8');
            console.log('配置保存成功');
        } catch (error) {
            console.error('配置保存失败:', error.message);
        }
    }
}

// 使用示例
const configManager = new ConfigManager('./config');
configManager.load();

console.log('应用名称:', configManager.get('app.name'));
console.log('数据库端口:', configManager.get('database.port'));

// 设置新值
configManager.set('app.port', 8080);
configManager.set('features.newFeature', true);

// 监听变化
configManager.watch('app.port', (key, value) => {
    console.log(`配置 ${key} 变更为 ${value}`);
});

// 保存配置
configManager.save('development');

7. 核心案例速查与知识点归纳

7.1 案例学习路线

步骤 目录主题 验证要点
内置模块 fs join(__dirname)datas/a.txt
其他内置模块 url.parse / new URLqs.parse
异常处理 new Errorthrowtry/catch
JSON parse / stringify 读写 data01.json
CommonJS 01-mod~08-mod 暴露与 require
ES6 模块 index.mjs + package.json "type":"module"
流式 createReadStreampipe → 复制
目录 mkdir / rmdir / readdir / access / stat

7.2 fs API 速查

需求 API 备注
读文本 readFile(p, 'utf-8', cb) 不指定编码得 Buffer
写覆盖 writeFile 文件不存在会创建
追加 appendFile 日志场景
改名/移动 rename 可跨目录
删文件 unlink 不能删非空目录
建目录 mkdirSync(dir, { recursive: true }) 多级一次创建
删目录 rmdir(dir, { recursive: true }) Node 14+ 递归删
列目录 readdir 返回文件名数组
是否存在 access / existsSync 高并发慎用同步
大文件 createReadStream.pipe(ws) 省内存

7.3 CommonJS 暴露/import 速查

写法 结果
无导出 require{}
module.exports = fn 得该函数/对象
exports.x = 1 { x: 1 }
exports = {} ❌ 与 module.exports 脱钩
require('./x.json') 直接解析为对象
require('./dir') package.jsonmainindex.js

【代码注释】

  • 第一次 require('./02-mod') 会执行模块顶层代码并把 module.exports 放进 require.cache;之后同一绝对路径的 require 直接返回缓存,不再执行顶层。
  • 开发时若改了模块需重启进程,或 delete require.cache[require.resolve('./02-mod')](仅调试用,生产勿依赖)。
  • require('./05-mod.json') 无需 .json 以外的解析,返回深拷贝前的对象;require('./目录') 解析 package.jsonmainindex.js
  • exports = {} 会断开与 module.exports 的引用,导致 require 拿到空对象------课堂 04-mod 常见考点。

7.4 ESM 启用方式

方式 配置
后缀 使用 .mjs
项目级 package.json"type": "module",则 .js 按 ESM 解析

【代码注释】

  • .mjs 文件始终按 ESM 解析;或在 package.json"type": "module" 使 .js 也按 ESM 解析(课堂 index.mjs + 配置案例)。
  • export default 每个模块只能有一个import xxx from './m.mjs' 导入默认导出。
  • 命名导出可多个:export const a = 1;export { a, b };导入需 import { a, b } from '...' 且名称一致(或用 as 重命名)。
  • ESM 的 import 提升且静态分析,利于 Tree Shaking;动态加载用 import('./mod.mjs') 返回 Promise。

7.5 实战:容量单位转换模块(CJS + ESM)

CommonJS 版:

javascript 复制代码
// convert-byte.js
const convertByte = (bytes, type = 0) => bytes / 1024 ** type;
module.exports = convertByte;
javascript 复制代码
// main.js
const convertByte = require('./convert-byte');
console.log('MB:', convertByte(1048576, 2));

ESM 版:

javascript 复制代码
// convert-byte.mjs
export default function convertByte(bytes, type = 0) {
    return bytes / 1024 ** type;
}
javascript 复制代码
// main.mjs
import convertByte from './convert-byte.mjs';
console.log('GB:', convertByte(1073741824, 3));

【代码注释】

  • convertByte(bytes, type)bytes / 1024 ** type 做进制换算:type=0 字节,1 KB,2 MB,3 GB,4 TB(基于 1024,非 1000)。
  • CJS:module.exports = fn + require('./convert-byte');ESM:export default + import convertByte from './convert-byte.mjs'
  • 1048576 字节 = 1MB(1024²);1073741824 = 1GB,可用控制台验证与课堂 main.js / main.mjs 输出一致。
  • 该模块可发布为 npm 包供他人 require,与 Day08 包管理章节衔接。

7.6 可运行 HTML:JSON 与 querystring 对照

保存为 json-query-demo.html,在浏览器打开(理解服务端 JSON.parse / qs.parse 的输入从哪来)。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>JSON 与查询串演示</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
    textarea { width: 100%; height: 120px; font-family: monospace; }
    pre { background: #1e1e1e; color: #d4d4d4; padding: 12px; border-radius: 8px; overflow: auto; }
    button { margin: 8px 8px 8px 0; padding: 8px 16px; cursor: pointer; }
  </style>
</head>
<body>
  <h1>JSON.parse 演示</h1>
  <textarea id="jsonIn">{ "name": "Node", "skills": ["fs", "path"] }</textarea>
  <button type="button" id="btnParse">解析 JSON</button>
  <pre id="jsonOut">点击解析...</pre>

  <h1>查询串(对应 qs.parse)</h1>
  <p>当前 URL 查询部分:<code id="search"></code></p>
  <pre id="qsOut"></pre>

  <script>
    document.getElementById('btnParse').onclick = () => {
      const raw = document.getElementById('jsonIn').value;
      try {
        const obj = JSON.parse(raw);
        document.getElementById('jsonOut').textContent = JSON.stringify(obj, null, 2);
      } catch (e) {
        document.getElementById('jsonOut').textContent = '解析失败: ' + e.message;
      }
    };

    const search = location.search.slice(1);
    document.getElementById('search').textContent = search || '(无)';
    const params = Object.fromEntries(new URLSearchParams(search));
    document.getElementById('qsOut').textContent = JSON.stringify(params, null, 2);
  </script>
</body>
</html>

【代码注释】

  • JSON.parse 解析请求体 或配置文件;页面里 try/catch 演示非法 JSON 时的 SyntaxError.message
  • location.search.slice(1) 去掉 ? 得到查询串;URLSearchParams 与 Node 的 querystring.parse 一样把 a=1&b=2 转为对象(值均为字符串)。
  • 浏览器地址栏加 ?wd=node&type=api 刷新,观察 qsOut 输出,对应服务端 url.parse(req.url).querynew URL(req.url, 'http://x').searchParams
  • 表单 application/x-www-form-urlencoded 与查询串格式相同,Express 中由中间件解析为 req.query / req.body

7.7 常见错误码

code 场景
ENOENT 路径不存在
EACCES 无读写权限
EISDIR 对目录执行了读文件
EEXIST 创建已存在目录(未设 recursive

总结

本教程深入解析了 Node.js 的核心模块和模块化系统,涵盖了从基础概念到高级应用的完整知识体系。通过学习本教程,您应该能够:

  1. 熟练掌握 Node.js 内置模块:path、fs、url、querystring 等模块的使用方法
  2. 理解并应用模块化规范:CommonJS 和 ES6 模块系统的区别和使用场景
  3. 实现文件操作功能:读写、复制、移动、删除等文件系统操作
  4. 构建企业级应用:日志系统、配置管理、文件服务器等实战项目
  5. 遵循最佳实践:错误处理、安全考虑、性能优化等方面的专业实践

学习建议

  1. 实践为主:每个知识点都要亲自编写代码进行验证
  2. 项目驱动:通过实际项目来巩固所学知识
  3. 阅读源码:研究优秀的开源项目,学习设计模式和最佳实践
  4. 持续更新:Node.js 发展迅速,要关注新版本的新特性和改进

参考资源

希望本教程能帮助您在 Node.js 开发之路上走得更远!

相关推荐
胖胖雕2 小时前
LLM增强的网易云API部署用于鸿蒙原生音乐app: Melotopia
docker·node.js·harmony
meilindehuzi_a14 小时前
全栈 AI 必修课:基于 Node.js 与 LLM 的渐进式提示词工程实践
人工智能·node.js·prompt
不好听61315 小时前
Prompt 驱动 NLP:用大语言模型重新定义自然语言处理开发范式
设计模式·node.js·nlp
触底反弹15 小时前
大模型时代:5 个 Prompt 替代 BERT 训练,搞定 NLP 五大任务
人工智能·node.js·api
甜味弥漫17 小时前
React 快速入门:从 JSX 到列表渲染
react.js·前端框架·node.js
用户938515635071 天前
从模块化到 Prompt 工程:我用 Node.js + LLM 复刻了传统 NLP 的流程
javascript·人工智能·node.js
妖孽白YoonA1 天前
xlt-token v1.0.0 正式发布:NestJS / Express 一包接入的 Token 鉴权库
后端·node.js·nestjs
码农阿豪2 天前
Node.js 连金仓数据库(下篇):连接池、事务和那些坑
数据库·node.js
晓杰'2 天前
从0到1实现Balatro游戏后端(7):Boss Blind与特殊规则实现
后端·websocket·typescript·node.js·游戏开发·项目实战·nestjs