Day06_Node.js 核心技术深度解析

一篇面向实战的 Node 入门博客:从 运行环境、REPL、Bufferpath / fs 文件系统 的完整基础。示例可独立运行,不依赖外部讲义路径。

官方网站:nodejs.org | 中文文档:nodejs.cn

目录

  • 导读:知识架构与权威参考
  • [1. Node.js 概述](#1. Node.js 概述)
    • [1.1 什么是Node.js](#1.1 什么是Node.js)
    • [1.2 为什么要学习Node.js](#1.2 为什么要学习Node.js)
    • [1.3 前端与后端开发的区别](#1.3 前端与后端开发的区别)
    • [1.4 Node.js的核心特性](#1.4 Node.js的核心特性)
  • [2. Node.js 安装与使用](#2. Node.js 安装与使用)
    • [2.1 版本选择策略](#2.1 版本选择策略)
    • [2.2 REPL交互式环境](#2.2 REPL交互式环境)
    • [2.3 脚本执行方式](#2.3 脚本执行方式)
    • [2.4 常用开发工具](#2.4 常用开发工具)
  • [3. 内置常量与全局对象](#3. 内置常量与全局对象)
    • [3.1 __dirname - 当前目录绝对路径](#3.1 __dirname - 当前目录绝对路径)
    • [3.2 __filename - 当前脚本绝对路径](#3.2 __filename - 当前脚本绝对路径)
    • [3.3 global - 全局对象](#3.3 global - 全局对象)
    • [3.4 process - 进程对象](#3.4 process - 进程对象)
  • [4. Buffer 深度解析](#4. Buffer 深度解析)
    • [4.1 Buffer基础概念](#4.1 Buffer基础概念)
    • [4.2 Buffer创建方式](#4.2 Buffer创建方式)
    • [4.3 Buffer读写操作](#4.3 Buffer读写操作)
    • [4.4 Buffer高级特性](#4.4 Buffer高级特性)
    • [4.5 实际应用场景](#4.5 实际应用场景)
  • [5. 内置模块详解](#5. 内置模块详解)
    • [5.0 Node 模块分类与引入方式](#5.0 Node 模块分类与引入方式)
    • [5.1 path路径模块](#5.1 path路径模块)
      • [5.1.4 path.parse与path.format](#5.1.4 path.parse与path.format)
    • [5.2 fs文件系统模块](#5.2 fs文件系统模块)
      • [5.2.5 文件监听:fs.watch](#5.2.5 文件监听:fs.watch)
      • [5.2.6 实际应用场景](#5.2.6 实际应用场景)
  • [6. 最佳实践与性能优化](#6. 最佳实践与性能优化)
    • [6.1 异步编程最佳实践](#6.1 异步编程最佳实践)
    • [6.2 文件操作性能优化](#6.2 文件操作性能优化)
    • [6.3 错误处理策略](#6.3 错误处理策略)
    • [6.4 Stream 流式处理](#6.4 Stream 流式处理)
  • [7. 总结与进阶学习路径](#7. 总结与进阶学习路径)
    • [7.1 核心知识点总结](#7.1 核心知识点总结)
    • [7.2 学习路径建议](#7.2 学习路径建议)
    • [7.3 常见问题解答](#7.3 常见问题解答)
    • [7.4 实用资源推荐](#7.4 实用资源推荐)
  • [8. 核心案例速查与知识点归纳](#8. 核心案例速查与知识点归纳)
    • [8.1 案例学习路线(按顺序练)](#8.1 案例学习路线(按顺序练))
    • [8.2 Buffer 速查](#8.2 Buffer 速查)
    • [8.3 path 与 fs 对照](#8.3 path 与 fs 对照)
    • [8.4 同步 vs 异步选择(归纳)](#8.4 同步 vs 异步选择(归纳))
    • [8.5 可运行 HTML 演示页(理解 Buffer 与文本)](#8.5 可运行 HTML 演示页(理解 Buffer 与文本))
    • [8.6 常见错误码](#8.6 常见错误码)
    • [8.7 process 快速参考](#8.7 process 快速参考)

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

本文解决什么问题

模块 你会掌握 典型场景
Node 概述 V8、事件驱动、非阻塞 I/O 理解为何适合 I/O 密集型服务
安装使用 LTS、REPL、node script.js 本地跑脚本、快速试 API
内置常量 __dirname__filenameglobal 拼路径、CLI 工具
Buffer alloc / from、读写、溢出 文件二进制、网络包
path joinresolveextname 跨平台路径、构建工具
fs 读/写/重命名/删除/目录 日志、配置、静态资源

知识脉络(Mermaid)

#mermaid-svg-ukWSVzbbVUY2JVRK{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-ukWSVzbbVUY2JVRK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ukWSVzbbVUY2JVRK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ukWSVzbbVUY2JVRK .error-icon{fill:#552222;}#mermaid-svg-ukWSVzbbVUY2JVRK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ukWSVzbbVUY2JVRK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ukWSVzbbVUY2JVRK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ukWSVzbbVUY2JVRK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ukWSVzbbVUY2JVRK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ukWSVzbbVUY2JVRK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ukWSVzbbVUY2JVRK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ukWSVzbbVUY2JVRK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ukWSVzbbVUY2JVRK .marker.cross{stroke:#333333;}#mermaid-svg-ukWSVzbbVUY2JVRK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ukWSVzbbVUY2JVRK p{margin:0;}#mermaid-svg-ukWSVzbbVUY2JVRK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ukWSVzbbVUY2JVRK .cluster-label text{fill:#333;}#mermaid-svg-ukWSVzbbVUY2JVRK .cluster-label span{color:#333;}#mermaid-svg-ukWSVzbbVUY2JVRK .cluster-label span p{background-color:transparent;}#mermaid-svg-ukWSVzbbVUY2JVRK .label text,#mermaid-svg-ukWSVzbbVUY2JVRK span{fill:#333;color:#333;}#mermaid-svg-ukWSVzbbVUY2JVRK .node rect,#mermaid-svg-ukWSVzbbVUY2JVRK .node circle,#mermaid-svg-ukWSVzbbVUY2JVRK .node ellipse,#mermaid-svg-ukWSVzbbVUY2JVRK .node polygon,#mermaid-svg-ukWSVzbbVUY2JVRK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ukWSVzbbVUY2JVRK .rough-node .label text,#mermaid-svg-ukWSVzbbVUY2JVRK .node .label text,#mermaid-svg-ukWSVzbbVUY2JVRK .image-shape .label,#mermaid-svg-ukWSVzbbVUY2JVRK .icon-shape .label{text-anchor:middle;}#mermaid-svg-ukWSVzbbVUY2JVRK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ukWSVzbbVUY2JVRK .rough-node .label,#mermaid-svg-ukWSVzbbVUY2JVRK .node .label,#mermaid-svg-ukWSVzbbVUY2JVRK .image-shape .label,#mermaid-svg-ukWSVzbbVUY2JVRK .icon-shape .label{text-align:center;}#mermaid-svg-ukWSVzbbVUY2JVRK .node.clickable{cursor:pointer;}#mermaid-svg-ukWSVzbbVUY2JVRK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ukWSVzbbVUY2JVRK .arrowheadPath{fill:#333333;}#mermaid-svg-ukWSVzbbVUY2JVRK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ukWSVzbbVUY2JVRK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ukWSVzbbVUY2JVRK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ukWSVzbbVUY2JVRK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ukWSVzbbVUY2JVRK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ukWSVzbbVUY2JVRK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ukWSVzbbVUY2JVRK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ukWSVzbbVUY2JVRK .cluster text{fill:#333;}#mermaid-svg-ukWSVzbbVUY2JVRK .cluster span{color:#333;}#mermaid-svg-ukWSVzbbVUY2JVRK 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-ukWSVzbbVUY2JVRK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ukWSVzbbVUY2JVRK rect.text{fill:none;stroke-width:0;}#mermaid-svg-ukWSVzbbVUY2JVRK .icon-shape,#mermaid-svg-ukWSVzbbVUY2JVRK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ukWSVzbbVUY2JVRK .icon-shape p,#mermaid-svg-ukWSVzbbVUY2JVRK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ukWSVzbbVUY2JVRK .icon-shape .label rect,#mermaid-svg-ukWSVzbbVUY2JVRK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ukWSVzbbVUY2JVRK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ukWSVzbbVUY2JVRK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ukWSVzbbVUY2JVRK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Node.js 运行时
内置常量
Buffer 二进制
path 路径
fs 文件系统
工程化 / HTTP / Express

权威文档

主题 链接
Node.js 总览 nodejs.org/api
Buffer nodejs.org/api/buffer.html
path nodejs.org/api/path.html
fs nodejs.org/api/fs.html
事件循环 nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick

与前端工程化的关系

  • Webpack / Vite / Rollup :在 Node 环境执行,读写磁盘、解析 path、处理 Buffer
  • npm scriptsnode build.js 即本章「脚本方式运行」。
  • Electron :桌面端同样基于 Node + Chromium,文件 API 与 fs 相通。

1. Node.js 概述

1.1 什么是Node.js

名词解析:

  • JavaScript运行环境(Runtime Environment):为JavaScript代码提供执行环境的软件系统,包含解析器、内存管理和系统API。
  • Chrome V8引擎:Google Chrome浏览器使用的开源JavaScript引擎,负责将JavaScript代码编译为机器码执行。

技术定义:

Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,让JavaScript能够脱离浏览器在服务器端运行。它使用事件驱动、非阻塞I/O模型,使其轻量又高效。

架构对比:
#mermaid-svg-yZf0HFB6Tlo7rlu0{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-yZf0HFB6Tlo7rlu0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .error-icon{fill:#552222;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .marker.cross{stroke:#333333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yZf0HFB6Tlo7rlu0 p{margin:0;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .cluster-label text{fill:#333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .cluster-label span{color:#333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .cluster-label span p{background-color:transparent;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .label text,#mermaid-svg-yZf0HFB6Tlo7rlu0 span{fill:#333;color:#333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .node rect,#mermaid-svg-yZf0HFB6Tlo7rlu0 .node circle,#mermaid-svg-yZf0HFB6Tlo7rlu0 .node ellipse,#mermaid-svg-yZf0HFB6Tlo7rlu0 .node polygon,#mermaid-svg-yZf0HFB6Tlo7rlu0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .rough-node .label text,#mermaid-svg-yZf0HFB6Tlo7rlu0 .node .label text,#mermaid-svg-yZf0HFB6Tlo7rlu0 .image-shape .label,#mermaid-svg-yZf0HFB6Tlo7rlu0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .rough-node .label,#mermaid-svg-yZf0HFB6Tlo7rlu0 .node .label,#mermaid-svg-yZf0HFB6Tlo7rlu0 .image-shape .label,#mermaid-svg-yZf0HFB6Tlo7rlu0 .icon-shape .label{text-align:center;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .node.clickable{cursor:pointer;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .arrowheadPath{fill:#333333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yZf0HFB6Tlo7rlu0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yZf0HFB6Tlo7rlu0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yZf0HFB6Tlo7rlu0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .cluster text{fill:#333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .cluster span{color:#333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 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-yZf0HFB6Tlo7rlu0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yZf0HFB6Tlo7rlu0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .icon-shape,#mermaid-svg-yZf0HFB6Tlo7rlu0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .icon-shape p,#mermaid-svg-yZf0HFB6Tlo7rlu0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .icon-shape .label rect,#mermaid-svg-yZf0HFB6Tlo7rlu0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yZf0HFB6Tlo7rlu0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yZf0HFB6Tlo7rlu0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yZf0HFB6Tlo7rlu0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Node.js环境
浏览器环境
JavaScript代码
浏览器API
DOM操作
网络请求
本地存储
JavaScript代码
Node.js API
文件系统
网络服务
进程管理
操作系统交互
V8引擎

1.2 为什么要学习Node.js

核心价值:

  1. 全栈开发能力

    • 前端工程师可快速转型为全栈工程师
    • 统一使用JavaScript语言,降低学习成本
    • 代码复用率提升,团队协作效率提高
  2. 前端工程化基础

    • 所有现代前端构建工具(Webpack、Vite、Rollup)都基于Node.js
    • 包管理器(npm、yarn、pnpm)为项目依赖管理提供支持
    • 前端脚手架工具(Vue CLI、Create React App)都运行在Node环境
  3. 自动化工具开发

    • 构建自动化脚本
    • 数据爬虫程序
    • CI/CD流程脚本
    • 部署工具
  4. 跨平台应用开发

    • Electron:构建跨平台桌面应用(VSCode、Slack、Discord)
    • React Native:移动应用开发框架
    • NW.js:另一种桌面应用解决方案

市场应用案例:

应用类型 代表产品 使用场景
桌面应用 VSCode 代码编辑器
桌面应用 Slack 团队协作工具
桌面应用 Discord 游戏社区平台
构建工具 Webpack 前端资源打包
构建工具 Vite 新一代构建工具
API服务 PayPal 支付平台API
实时应用 Uber 实时请求处理

1.3 前端与后端开发的区别

概念对比:
#mermaid-svg-fe4FHpb6FF3sQriq{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-fe4FHpb6FF3sQriq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fe4FHpb6FF3sQriq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fe4FHpb6FF3sQriq .error-icon{fill:#552222;}#mermaid-svg-fe4FHpb6FF3sQriq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fe4FHpb6FF3sQriq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fe4FHpb6FF3sQriq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fe4FHpb6FF3sQriq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fe4FHpb6FF3sQriq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fe4FHpb6FF3sQriq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fe4FHpb6FF3sQriq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fe4FHpb6FF3sQriq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fe4FHpb6FF3sQriq .marker.cross{stroke:#333333;}#mermaid-svg-fe4FHpb6FF3sQriq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fe4FHpb6FF3sQriq p{margin:0;}#mermaid-svg-fe4FHpb6FF3sQriq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fe4FHpb6FF3sQriq .cluster-label text{fill:#333;}#mermaid-svg-fe4FHpb6FF3sQriq .cluster-label span{color:#333;}#mermaid-svg-fe4FHpb6FF3sQriq .cluster-label span p{background-color:transparent;}#mermaid-svg-fe4FHpb6FF3sQriq .label text,#mermaid-svg-fe4FHpb6FF3sQriq span{fill:#333;color:#333;}#mermaid-svg-fe4FHpb6FF3sQriq .node rect,#mermaid-svg-fe4FHpb6FF3sQriq .node circle,#mermaid-svg-fe4FHpb6FF3sQriq .node ellipse,#mermaid-svg-fe4FHpb6FF3sQriq .node polygon,#mermaid-svg-fe4FHpb6FF3sQriq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fe4FHpb6FF3sQriq .rough-node .label text,#mermaid-svg-fe4FHpb6FF3sQriq .node .label text,#mermaid-svg-fe4FHpb6FF3sQriq .image-shape .label,#mermaid-svg-fe4FHpb6FF3sQriq .icon-shape .label{text-anchor:middle;}#mermaid-svg-fe4FHpb6FF3sQriq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fe4FHpb6FF3sQriq .rough-node .label,#mermaid-svg-fe4FHpb6FF3sQriq .node .label,#mermaid-svg-fe4FHpb6FF3sQriq .image-shape .label,#mermaid-svg-fe4FHpb6FF3sQriq .icon-shape .label{text-align:center;}#mermaid-svg-fe4FHpb6FF3sQriq .node.clickable{cursor:pointer;}#mermaid-svg-fe4FHpb6FF3sQriq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fe4FHpb6FF3sQriq .arrowheadPath{fill:#333333;}#mermaid-svg-fe4FHpb6FF3sQriq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fe4FHpb6FF3sQriq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fe4FHpb6FF3sQriq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fe4FHpb6FF3sQriq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fe4FHpb6FF3sQriq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fe4FHpb6FF3sQriq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fe4FHpb6FF3sQriq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fe4FHpb6FF3sQriq .cluster text{fill:#333;}#mermaid-svg-fe4FHpb6FF3sQriq .cluster span{color:#333;}#mermaid-svg-fe4FHpb6FF3sQriq 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-fe4FHpb6FF3sQriq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fe4FHpb6FF3sQriq rect.text{fill:none;stroke-width:0;}#mermaid-svg-fe4FHpb6FF3sQriq .icon-shape,#mermaid-svg-fe4FHpb6FF3sQriq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fe4FHpb6FF3sQriq .icon-shape p,#mermaid-svg-fe4FHpb6FF3sQriq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fe4FHpb6FF3sQriq .icon-shape .label rect,#mermaid-svg-fe4FHpb6FF3sQriq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fe4FHpb6FF3sQriq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fe4FHpb6FF3sQriq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fe4FHpb6FF3sQriq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 静态资源
数据请求
用户请求
请求类型
前端代码
后端服务
浏览器渲染
用户交互
业务逻辑处理
数据库操作
API响应

技术栈对比:

维度 前端开发 后端开发
运行环境 浏览器 服务器
核心任务 用户界面、交互体验 业务逻辑、数据处理
主要技术 HTML/CSS/JavaScript 各种编程语言及框架
存储访问 Cookie、LocalStorage 数据库、缓存系统
安全关注点 XSS、CSRF防护 SQL注入、认证授权

Node.js在后端开发中的优势:

  • 统一语言栈:前后端都使用JavaScript,减少上下文切换
  • 高并发处理:事件驱动模型适合I/O密集型应用
  • 微服务架构:轻量级特性使其成为微服务的理想选择
  • 快速开发:丰富的npm生态系统,开箱即用的解决方案

1.4 Node.js的核心特性

1. 单线程(Single Threaded)
EventLoop NodeJS Client EventLoop NodeJS Client #mermaid-svg-j8qgLtzWwN7lymYM{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-j8qgLtzWwN7lymYM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j8qgLtzWwN7lymYM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j8qgLtzWwN7lymYM .error-icon{fill:#552222;}#mermaid-svg-j8qgLtzWwN7lymYM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j8qgLtzWwN7lymYM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j8qgLtzWwN7lymYM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j8qgLtzWwN7lymYM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j8qgLtzWwN7lymYM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j8qgLtzWwN7lymYM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j8qgLtzWwN7lymYM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j8qgLtzWwN7lymYM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j8qgLtzWwN7lymYM .marker.cross{stroke:#333333;}#mermaid-svg-j8qgLtzWwN7lymYM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j8qgLtzWwN7lymYM p{margin:0;}#mermaid-svg-j8qgLtzWwN7lymYM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j8qgLtzWwN7lymYM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-j8qgLtzWwN7lymYM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-j8qgLtzWwN7lymYM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-j8qgLtzWwN7lymYM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-j8qgLtzWwN7lymYM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-j8qgLtzWwN7lymYM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-j8qgLtzWwN7lymYM .sequenceNumber{fill:white;}#mermaid-svg-j8qgLtzWwN7lymYM #sequencenumber{fill:#333;}#mermaid-svg-j8qgLtzWwN7lymYM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-j8qgLtzWwN7lymYM .messageText{fill:#333;stroke:none;}#mermaid-svg-j8qgLtzWwN7lymYM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j8qgLtzWwN7lymYM .labelText,#mermaid-svg-j8qgLtzWwN7lymYM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-j8qgLtzWwN7lymYM .loopText,#mermaid-svg-j8qgLtzWwN7lymYM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-j8qgLtzWwN7lymYM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-j8qgLtzWwN7lymYM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-j8qgLtzWwN7lymYM .noteText,#mermaid-svg-j8qgLtzWwN7lymYM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-j8qgLtzWwN7lymYM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j8qgLtzWwN7lymYM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j8qgLtzWwN7lymYM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j8qgLtzWwN7lymYM .actorPopupMenu{position:absolute;}#mermaid-svg-j8qgLtzWwN7lymYM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-j8qgLtzWwN7lymYM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j8qgLtzWwN7lymYM .actor-man circle,#mermaid-svg-j8qgLtzWwN7lymYM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-j8qgLtzWwN7lymYM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求1 请求2 请求3 放入事件队列 逐个处理 响应

技术原理:

Node.js采用单线程事件循环模型,主线程负责执行JavaScript代码,而I/O操作由系统内核或线程池处理,完成后通过事件通知主线程。

优势分析:

  • 内存占用低:不需要为每个请求创建新线程
  • 无锁竞争:单线程避免了多线程的锁和状态同步问题
  • 状态简单:编程模型简单,不需要考虑并发问题

适用场景:

  • I/O密集型应用(Web服务、API服务)
  • 实时应用(聊天、游戏)
  • 不适合CPU密集型计算

2. 非阻塞I/O(Non-blocking I/O)

概念解析:

  • 阻塞I/O:操作完成前,线程会等待,无法处理其他任务
  • 非阻塞I/O:操作发起后立即返回,通过回调函数处理结果

代码对比:

javascript 复制代码
// 阻塞方式(伪代码)
const data = readFile('file.txt');  // 等待读取完成
console.log(data);                  // 处理数据
console.log('其他操作');            // 必须等待上面完成

// 非阻塞方式(Node.js)
readFile('file.txt', (err, data) => {
    console.log(data);              // 读取完成后执行
});
console.log('其他操作');            // 立即执行,不等待

3. 事件驱动(Event-driven)
#mermaid-svg-QK9Gdizl1RxbZGof{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-QK9Gdizl1RxbZGof .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QK9Gdizl1RxbZGof .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QK9Gdizl1RxbZGof .error-icon{fill:#552222;}#mermaid-svg-QK9Gdizl1RxbZGof .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QK9Gdizl1RxbZGof .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QK9Gdizl1RxbZGof .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QK9Gdizl1RxbZGof .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QK9Gdizl1RxbZGof .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QK9Gdizl1RxbZGof .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QK9Gdizl1RxbZGof .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QK9Gdizl1RxbZGof .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QK9Gdizl1RxbZGof .marker.cross{stroke:#333333;}#mermaid-svg-QK9Gdizl1RxbZGof svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QK9Gdizl1RxbZGof p{margin:0;}#mermaid-svg-QK9Gdizl1RxbZGof .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QK9Gdizl1RxbZGof .cluster-label text{fill:#333;}#mermaid-svg-QK9Gdizl1RxbZGof .cluster-label span{color:#333;}#mermaid-svg-QK9Gdizl1RxbZGof .cluster-label span p{background-color:transparent;}#mermaid-svg-QK9Gdizl1RxbZGof .label text,#mermaid-svg-QK9Gdizl1RxbZGof span{fill:#333;color:#333;}#mermaid-svg-QK9Gdizl1RxbZGof .node rect,#mermaid-svg-QK9Gdizl1RxbZGof .node circle,#mermaid-svg-QK9Gdizl1RxbZGof .node ellipse,#mermaid-svg-QK9Gdizl1RxbZGof .node polygon,#mermaid-svg-QK9Gdizl1RxbZGof .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QK9Gdizl1RxbZGof .rough-node .label text,#mermaid-svg-QK9Gdizl1RxbZGof .node .label text,#mermaid-svg-QK9Gdizl1RxbZGof .image-shape .label,#mermaid-svg-QK9Gdizl1RxbZGof .icon-shape .label{text-anchor:middle;}#mermaid-svg-QK9Gdizl1RxbZGof .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QK9Gdizl1RxbZGof .rough-node .label,#mermaid-svg-QK9Gdizl1RxbZGof .node .label,#mermaid-svg-QK9Gdizl1RxbZGof .image-shape .label,#mermaid-svg-QK9Gdizl1RxbZGof .icon-shape .label{text-align:center;}#mermaid-svg-QK9Gdizl1RxbZGof .node.clickable{cursor:pointer;}#mermaid-svg-QK9Gdizl1RxbZGof .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QK9Gdizl1RxbZGof .arrowheadPath{fill:#333333;}#mermaid-svg-QK9Gdizl1RxbZGof .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QK9Gdizl1RxbZGof .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QK9Gdizl1RxbZGof .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QK9Gdizl1RxbZGof .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QK9Gdizl1RxbZGof .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QK9Gdizl1RxbZGof .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QK9Gdizl1RxbZGof .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QK9Gdizl1RxbZGof .cluster text{fill:#333;}#mermaid-svg-QK9Gdizl1RxbZGof .cluster span{color:#333;}#mermaid-svg-QK9Gdizl1RxbZGof 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-QK9Gdizl1RxbZGof .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QK9Gdizl1RxbZGof rect.text{fill:none;stroke-width:0;}#mermaid-svg-QK9Gdizl1RxbZGof .icon-shape,#mermaid-svg-QK9Gdizl1RxbZGof .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QK9Gdizl1RxbZGof .icon-shape p,#mermaid-svg-QK9Gdizl1RxbZGof .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QK9Gdizl1RxbZGof .icon-shape .label rect,#mermaid-svg-QK9Gdizl1RxbZGof .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QK9Gdizl1RxbZGof .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QK9Gdizl1RxbZGof .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QK9Gdizl1RxbZGof :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件触发
事件队列
事件循环
检查事件
执行回调
返回结果

核心机制:

  • 事件循环:持续监听事件队列,处理待执行的事件
  • 回调函数:事件发生时执行的处理函数
  • 异步编程:通过事件机制实现非阻塞操作

实际应用:

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

const server = http.createServer((req, res) => {
    // 每个请求都是事件
    res.writeHead(200);
    res.end('Hello World');
});

server.listen(3000);  // 监听端口事件

2. Node.js 安装与使用

2.1 版本选择策略

版本类型对比:

版本类型 全称 特点 适用场景
LTS Long Term Support 长期支持版,稳定可靠 生产环境、企业级项目
Current Current Version 最新特性,可能不稳定 开发测试、尝鲜新特性

版本号规则:

Node.js采用语义化版本控制(Semantic Versioning):

  • 主版本号:不兼容的API修改
  • 次版本号:向下兼容的功能性新增
  • 修订号:向下兼容的问题修正

例如: v20.11.0

  • 20:主版本号
  • 11:次版本号
  • 0:修订号

下载地址:

【代码注释】

  • LTS(Long Term Support):官方长期维护分支,约 30 个月安全更新,适合线上服务、公司项目、教学环境;版本号偶数代(如 20、22)通常对应 LTS。
  • Current:包含最新 API 与实验特性,更新快,可能在次版本升级时出现行为变化,适合本地试新特性,不宜直接用于生产。
  • 团队应统一 Node 大版本(写在 .nvmrc 或 CI 配置里),避免「我本地 22、同事 18」导致 fs、OpenSSL 行为不一致。
  • 历史版本可从 nodejs.org/dist 或国内镜像下载;升级前在测试环境跑一遍 npm test

版本管理工具:

推荐使用nvm(Node Version Manager)管理多个Node版本:

bash 复制代码
# 安装nvm(macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# 常用命令
nvm install 20        # 安装指定版本
nvm use 20            # 切换版本
nvm ls                # 列出已安装版本
nvm alias default 20  # 设置默认版本

【代码注释】

  • nvm install 20 下载并编译/安装指定主版本;nvm use 20 仅对当前终端会话 生效,新开终端需再 use 或设置 default
  • 多项目可各放 .nvmrc(内容如 20),进入目录后执行 nvm use 自动切换,避免全局版本与项目要求不一致。
  • macOS/Linux 通过修改 ~/.bashrc / ~/.zshrc 加载 nvm;Windows 可使用 nvm-windows 独立工具。

2.2 REPL交互式环境

名词解析:REPL(Read-Eval-Print Loop)

  • Read:读取用户输入的代码
  • Eval:解析执行代码
  • Print:输出执行结果
  • Loop:循环等待新的输入

启动REPL:

bash 复制代码
$ node
>

基本操作:

javascript 复制代码
// 简单表达式
> 1 + 2
3

// 变量定义
> const name = 'Node.js'
undefined

// 函数定义
> const greet = (name) => `Hello, ${name}`
undefined

> greet('World')
'Hello, World'

常用命令:

命令 功能
.help 显示所有可用命令
.break 退出多行表达式输入
.clear 清除REPL上下文
.exit 退出REPL
.save filename 保存当前会话到文件
.load filename 从文件加载会话
Ctrl + C(两次) 强制退出REPL
Ctrl + D 退出REPL
↑/↓方向键 浏览历史命令
Tab 自动补全

实际应用场景:

  1. 快速测试代码片段
  2. 调试和实验API
  3. 学习和探索Node.js特性
  4. 原型验证

退出REPL:

javascript 复制代码
> .exit
$

2.3 脚本执行方式

基本语法:

bash 复制代码
node [options] [script.js] [arguments]

执行示例:

javascript 复制代码
// 创建文件 hello.js
console.log('Hello from Node.js!');
process.argv.forEach((val, index) => {
    console.log(`${index}: ${val}`);
});
bash 复制代码
# 执行脚本
node hello.js arg1 arg2

# 输出:
# Hello from Node.js!
# 0: /usr/local/bin/node
# 1: /path/to/hello.js
# 2: arg1
# 3: arg2

【代码注释】

  • process.argv[0]node 二进制路径,[1] 是当前执行的 hello.js 绝对路径,自定义参数从 [2] 开始编号。
  • 课堂可用 node hello.js foo bar 验证打印;编写 CLI 时常用 yargscommander 解析 argv,而不是手写 argv[2]

最小脚本示例(循环与 REPL 表达式):

javascript 复制代码
for (let i = 0; i < 10; i++) {
    console.log(i);
}
100;
bash 复制代码
node hello.js

【代码注释】

  • node hello.js 会按文件从上到下同步执行 整个脚本;for 循环会立刻跑完 0~9,最后一行 100; 的表达式结果在脚本里不会自动打印(REPL 里才会显示返回值)。
  • process.argv 是字符串数组:[0]node 可执行文件路径,[1] 为当前脚本绝对路径,从 [2] 起才是你在命令行传入的参数(如 arg1arg2)。
  • 与 REPL 对比:在 REPL 输入 for 会进入 ... 多行模式,需 .break 或空行结束;写进 .js 文件则没有这个问题。
  • node -e "..." / -p 适合一行表达式调试;-p 会打印最后一表达式的值,等价于 REPL 回显。

实用选项:

选项 功能
-v, --version 打印Node.js版本
-e, --eval 执行JavaScript代码
-p, --print 执行并打印结果
--check 语法检查,不执行

使用示例:

bash 复制代码
# 版本检查
node -v

# 直接执行代码
node -e "console.log(Math.random())"

# 计算并打印结果
node -p "Math.PI * 2"

# 语法检查
node --check script.js

2.4 常用开发工具

命令行工具:

平台 推荐工具 特点
Windows PowerShell 微软官方,功能强大
Windows Git Bash Git环境,类Unix命令
Windows Windows Terminal 现代化终端,支持多标签
macOS Terminal 系统自带终端
macOS iTerm2 功能丰富的第三方终端
Linux 各发行版终端 根据发行版选择

VSCode集成终端:

  1. 在VSCode中打开项目文件夹
  2. 使用快捷键 Ctrl + ~(或 Cmd + ~)打开终端
  3. 或右键文件夹选择"在集成终端中打开"

优势:

  • 无需切换窗口
  • 直接在项目目录中操作
  • 支持多个终端实例

课堂对照:命令行环境

平台 可用终端
Windows cmd、PowerShell、Git Bash
macOS 系统「终端」
VSCode 集成终端(Ctrl/Cmd + ~

【代码注释】

  • Node 里很多 API(如 path.resolve('./data')fs.readFile('./a.txt'))依赖当前工作目录 cwd ,即你执行 node 命令时终端所在文件夹,而不是脚本文件所在目录。
  • 在 VSCode 用「在集成终端中打开」或先 cd 到项目根再运行,可避免「脚本在 src/、数据在 data/ 却读不到文件」的 ENOENT。
  • Windows 可用 cmd / PowerShell / Git Bash;macOS 用「终端」;三者都能跑 node -v 验证环境变量 PATH 是否包含 Node 安装路径。

3. 内置常量与全局对象

3.1 __dirname - 当前目录绝对路径

概念:

__dirname 是Node.js的内置常量,返回当前执行脚本所在的目录的绝对路径。

语法:

javascript 复制代码
__dirname

返回值类型: 字符串(String)

使用示例:

javascript 复制代码
// 文件路径:/Users/demo/project/script.js
console.log(__dirname);
// 输出:/Users/demo/project

实际应用:

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

// 构建相对于当前脚本目录的文件路径
const configPath = path.join(__dirname, 'config.json');
const dataPath = path.join(__dirname, 'data', 'file.txt');

console.log(configPath);  // /Users/demo/project/config.json
console.log(dataPath);    // /Users/demo/project/data/file.txt

注意事项:

  • __dirname 在模块顶层作用域可用
  • 在ES模块中需要通过 import.meta.url 获取
  • 不是真正的全局变量,而是每个模块的本地变量

3.2 __filename - 当前脚本绝对路径

概念:

__filename 返回当前正在执行的脚本的绝对路径。

语法:

javascript 复制代码
__filename

返回值类型: 字符串(String)

使用示例:

javascript 复制代码
// 文件路径:/Users/demo/project/script.js
console.log(__filename);
// 输出:/Users/demo/project/script.js

实际应用:

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

// 获取文件所在目录
const dir = path.dirname(__filename);

// 获取文件名
const basename = path.basename(__filename);

// 获取扩展名
const extname = path.extname(__filename);

console.log('目录:', dir);       // /Users/demo/project
console.log('文件名:', basename); // script.js
console.log('扩展名:', extname);  // .js

应用场景:

  1. 日志记录:记录日志来源文件
  2. 错误追踪:调试时定位问题文件
  3. 文件路径处理:构建相对路径

3.3 global - 全局对象

概念:

global 是Node.js的全局命名空间,类似于浏览器中的 window 对象。

全局属性和方法:

属性/方法 描述
console 控制台输出对象
process 当前进程信息
Buffer 二进制数据处理
setTimeout() 定时执行
setInterval() 循环执行
setImmediate() 立即执行
clearTimeout() 清除定时器
clearInterval() 清除循环定时器
clearImmediate() 清除立即执行

使用示例:

javascript 复制代码
// 定义全局变量
global.myApp = {
    name: 'My Application',
    version: '1.0.0'
};

// 在任何地方访问
console.log(global.myApp.name);  // My Application

// 直接访问(global的属性可省略global前缀)
console.log(myApp.name);         // My Application

// 查看所有全局对象
console.log(global);

【代码注释】

  • __dirname:当前模块文件 所在目录的绝对路径(不含文件名),拼 config.json、静态资源时比 ./ 可靠,因为 cwd 可能任意。
  • __filename:当前模块文件的绝对路径;配合 path.dirname / basename / extname 可拆分目录名、文件名、扩展名,常用于日志标注「错误来自哪一行文件」。
  • 二者在每个 CommonJS 模块里各有一份,不是真正的全局变量;在浏览器里没有这两个常量。
  • ESM("type":"module".mjs)中应写:import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename);
  • global 类似浏览器的 window,但应避免 global.xxx = 污染命名空间;共享配置更推荐单独 config.js 模块导出。

注意事项:

  • 避免污染全局命名空间
  • 谨慎使用全局变量,优先使用模块作用域
  • 在不同模块间共享状态时考虑使用专门的模块

3.4 process - 进程对象

概念:

process 是 Node.js 内置的全局对象,提供当前 Node 进程的信息与控制接口,无需 require 即可直接使用。它是连接 Node.js 应用与操作系统的桥梁。

核心属性与方法:

属性/方法 描述 典型用途
process.env 环境变量对象 读取 NODE_ENVPORT、数据库连接串
process.argv 命令行参数数组 CLI 工具解析参数
process.cwd() 当前工作目录 构建相对路径
process.exit(code) 退出进程 脚本出错时主动终止
process.platform 操作系统平台 区分 win32/darwin/linux
process.version Node.js 版本字符串 运行时版本兼容检测
process.memoryUsage() 内存占用信息 性能监控、泄漏排查
process.hrtime() 高精度计时 微秒级基准测试

1. 环境变量(process.env):

javascript 复制代码
// 读取环境变量------常用于区分开发/生产配置
const port = Number(process.env.PORT) || 3000;
const dbUrl = process.env.DATABASE_URL || 'mongodb://localhost/dev';
const isDev = process.env.NODE_ENV !== 'production';

console.log(`启动端口: ${port}`);
console.log(`数据库  : ${dbUrl}`);
console.log(`开发模式: ${isDev}`);

// 使用 dotenv 加载 .env 文件(npm install dotenv)
// require('dotenv').config();
// 之后 process.env.MY_SECRET 即可读到 .env 中定义的变量

【代码注释】

  • process.env 中的所有值都是字符串PORT=3000 读出来是 '3000',用于端口监听前务必 Number()parseInt 转换。
  • NODE_ENV 是约定俗成的环境标识,Webpack/Vite/Express 都会读它;不要在生产部署时忘记设置。
  • .env 文件不应提交到 git(加入 .gitignore),敏感信息(数据库密码、API Key)通过 CI/CD 注入。

2. 命令行参数(process.argv):

javascript 复制代码
// 执行:node deploy.js --env production --port 8080

// process.argv = [
//   '/usr/local/bin/node',  // [0] node 可执行文件路径
//   '/path/to/deploy.js',   // [1] 脚本绝对路径
//   '--env',                // [2] 第一个自定义参数
//   'production',           // [3]
//   '--port',               // [4]
//   '8080'                  // [5]
// ]

// 简单键值解析(生产 CLI 推荐用 commander / yargs)
function parseArgs(argv) {
    const result = {};
    const args = argv.slice(2);  // 从 [2] 开始才是业务参数
    for (let i = 0; i < args.length; i += 2) {
        const key = args[i].replace(/^--/, '');
        result[key] = args[i + 1];
    }
    return result;
}

const options = parseArgs(process.argv);
console.log(options);
// { env: 'production', port: '8080' }

const serverPort = Number(options.port) || 3000;
console.log(`监听端口: ${serverPort}`);

【代码注释】

  • argv[0] 是 Node 可执行路径,argv[1] 是脚本路径,用户传入参数从 argv[2] 开始,这是 Node 固定约定。
  • 上面的 parseArgs 只适合 --key value 格式;更复杂的场景(布尔 flag、子命令、帮助信息)应使用 commanderyargs
  • process.env 结合使用:--port 优先级高于 PORT 环境变量,方便本地临时覆盖。

3. 进程控制与生命周期钩子:

javascript 复制代码
// 退出钩子------进程退出前做清理(只能执行同步代码)
process.on('exit', (code) => {
    console.log(`进程退出,退出码: ${code}`);
    // 可以:关闭文件句柄、写最后一条日志
    // 不可以:setTimeout、数据库异步操作(会被忽略)
});

// 捕获未处理的 Promise 拒绝(Node 15+ 默认会使进程崩溃)
process.on('unhandledRejection', (reason, promise) => {
    console.error('未处理的 Promise 拒绝:', reason);
    process.exit(1);  // 1 表示异常退出
});

// 捕获同步未捕获的异常
process.on('uncaughtException', (err) => {
    console.error('未捕获的异常:', err.message);
    process.exit(1);
});

// 主动退出------脚本工具的常见模式
function requireNodeVersion(major) {
    const current = parseInt(process.version.slice(1));
    if (current < major) {
        console.error(`需要 Node.js ${major}+ 当前为 ${process.version}`);
        process.exit(1);
    }
}
requireNodeVersion(18);

4. 平台与性能检测:

javascript 复制代码
// 跨平台脚本
const os = process.platform;
const commands = {
    win32: 'dir',
    darwin: 'ls -la',
    linux: 'ls -la'
};
const listCommand = commands[os] || 'ls';
console.log(`当前平台: ${os},列目录命令: ${listCommand}`);

// 内存监控(单位:字节)
const mem = process.memoryUsage();
console.log(`堆已用  : ${(mem.heapUsed  / 1024 / 1024).toFixed(1)} MB`);
console.log(`堆总量  : ${(mem.heapTotal / 1024 / 1024).toFixed(1)} MB`);
console.log(`RSS     : ${(mem.rss       / 1024 / 1024).toFixed(1)} MB`);

// 高精度计时(纳秒)
const start = process.hrtime.bigint();
// ... 被测代码 ...
const end = process.hrtime.bigint();
console.log(`耗时: ${(end - start) / 1000000n} ms`);

【代码注释】

  • process.platform 在 Windows 返回 'win32'(即使是 64 位系统),macOS 返回 'darwin',Linux 返回 'linux'
  • memoryUsage().heapUsed 是 V8 JS 堆已用内存;rss(Resident Set Size)是进程占用的系统物理内存(含 C++ 扩展)。
  • process.hrtime.bigint() 精度达纳秒级,适合微基准测试;普通场景用 console.time 即可。
  • exit(0) 表示正常结束,非零值(常用 1)表示异常,CI/CD 脚本会依据退出码判断任务是否成功。

4. Buffer 深度解析

4.1 Buffer基础概念

名词解析:

  • Buffer(缓冲区):用于存储二进制数据的固定大小内存区域
  • 二进制数据:以0和1表示的计算机基础数据格式
  • 字节(Byte):计算机数据存储的基本单位,1字节=8位(bit)
  • 编码:将字符转换为字节序列的规则

技术原理:
#mermaid-svg-uBdATBuPqB18F7PI{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-uBdATBuPqB18F7PI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uBdATBuPqB18F7PI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uBdATBuPqB18F7PI .error-icon{fill:#552222;}#mermaid-svg-uBdATBuPqB18F7PI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uBdATBuPqB18F7PI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uBdATBuPqB18F7PI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uBdATBuPqB18F7PI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uBdATBuPqB18F7PI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uBdATBuPqB18F7PI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uBdATBuPqB18F7PI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uBdATBuPqB18F7PI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uBdATBuPqB18F7PI .marker.cross{stroke:#333333;}#mermaid-svg-uBdATBuPqB18F7PI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uBdATBuPqB18F7PI p{margin:0;}#mermaid-svg-uBdATBuPqB18F7PI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uBdATBuPqB18F7PI .cluster-label text{fill:#333;}#mermaid-svg-uBdATBuPqB18F7PI .cluster-label span{color:#333;}#mermaid-svg-uBdATBuPqB18F7PI .cluster-label span p{background-color:transparent;}#mermaid-svg-uBdATBuPqB18F7PI .label text,#mermaid-svg-uBdATBuPqB18F7PI span{fill:#333;color:#333;}#mermaid-svg-uBdATBuPqB18F7PI .node rect,#mermaid-svg-uBdATBuPqB18F7PI .node circle,#mermaid-svg-uBdATBuPqB18F7PI .node ellipse,#mermaid-svg-uBdATBuPqB18F7PI .node polygon,#mermaid-svg-uBdATBuPqB18F7PI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uBdATBuPqB18F7PI .rough-node .label text,#mermaid-svg-uBdATBuPqB18F7PI .node .label text,#mermaid-svg-uBdATBuPqB18F7PI .image-shape .label,#mermaid-svg-uBdATBuPqB18F7PI .icon-shape .label{text-anchor:middle;}#mermaid-svg-uBdATBuPqB18F7PI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uBdATBuPqB18F7PI .rough-node .label,#mermaid-svg-uBdATBuPqB18F7PI .node .label,#mermaid-svg-uBdATBuPqB18F7PI .image-shape .label,#mermaid-svg-uBdATBuPqB18F7PI .icon-shape .label{text-align:center;}#mermaid-svg-uBdATBuPqB18F7PI .node.clickable{cursor:pointer;}#mermaid-svg-uBdATBuPqB18F7PI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uBdATBuPqB18F7PI .arrowheadPath{fill:#333333;}#mermaid-svg-uBdATBuPqB18F7PI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uBdATBuPqB18F7PI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uBdATBuPqB18F7PI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uBdATBuPqB18F7PI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uBdATBuPqB18F7PI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uBdATBuPqB18F7PI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uBdATBuPqB18F7PI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uBdATBuPqB18F7PI .cluster text{fill:#333;}#mermaid-svg-uBdATBuPqB18F7PI .cluster span{color:#333;}#mermaid-svg-uBdATBuPqB18F7PI 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-uBdATBuPqB18F7PI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uBdATBuPqB18F7PI rect.text{fill:none;stroke-width:0;}#mermaid-svg-uBdATBuPqB18F7PI .icon-shape,#mermaid-svg-uBdATBuPqB18F7PI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uBdATBuPqB18F7PI .icon-shape p,#mermaid-svg-uBdATBuPqB18F7PI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uBdATBuPqB18F7PI .icon-shape .label rect,#mermaid-svg-uBdATBuPqB18F7PI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uBdATBuPqB18F7PI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uBdATBuPqB18F7PI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uBdATBuPqB18F7PI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 字符数据 'ABC'
编码转换
Buffer缓冲区
内存存储
0x41 0x42 0x43
65 66 67

数据单位换算:

复制代码
1 Byte(字节) = 8 bit(位)
1 KB(千字节) = 1024 Byte
1 MB(兆字节) = 1024 KB
1 GB(吉字节) = 1024 MB
1 TB(太字节) = 1024 GB

Buffer特点:

特性 描述
大小固定 创建时确定大小,无法动态调整
性能优越 直接操作内存,无需中间转换
元素固定 每个元素占用1字节(0-255)
类数组 支持数组索引访问和遍历

为什么需要Buffer:

  1. 二进制数据处理:处理图片、视频、音频等文件
  2. 网络通信:TCP/UDP协议传输二进制数据
  3. 文件操作:读写文件的底层实现
  4. 性能优化:减少数据拷贝,提升处理效率

实际应用案例:

  • 图片处理:Sharp、Jimp等图片处理库
  • 文件上传:处理multipart/form-data数据
  • 网络协议:HTTP、WebSocket等协议实现
  • 加密解密:crypto模块底层实现

4.2 Buffer创建方式

4.2.1 Buffer.alloc() - 安全创建

语法:

javascript 复制代码
Buffer.alloc(size[, fill[, encoding]])

参数:

  • size:Buffer大小(字节)
  • fill:填充值,默认为0
  • encoding:字符编码

返回值: 新的Buffer实例

使用示例:

javascript 复制代码
// 创建10字节空Buffer(全部填充0)
const buf1 = Buffer.alloc(10);
console.log(buf1);
// <Buffer 00 00 00 00 00 00 00 00 00 00>

// 创建并填充指定值
const buf2 = Buffer.alloc(10, 0x20);  // 填充空格(ASCII 32)
console.log(buf2);
// <Buffer 20 20 20 20 20 20 20 20 20 20>

// 使用字符填充
const buf3 = Buffer.alloc(10, 'a');
console.log(buf3);
// <Buffer 61 61 61 61 61 61 61 61 61 61>

// 数字溢出处理
const buf4 = Buffer.alloc(10, 257);
console.log(buf4);
// <Buffer 01 01 01 01 01 01 01 01 01 01>
// 解释:257的二进制是100000001,只保留低8位=00000001=1

【代码注释】

  • Buffer.alloc(size) 会向操作系统申请 size 字节并清零 (或按 fill 填充),控制台形如 <Buffer 00 00 ...>,适合「长度已知、内容稍后写入」的场景。
  • fill 为字符串时按 UTF-8 取首字节重复填充;fill 为数字时按无符号 8 位 写入,257 → 1257 % 256),不是四舍五入。
  • allocUnsafe 对比:alloc 多一步初始化,略慢但无「上一段内存残留」风险;处理密钥、会话 token 等敏感数据应优先 alloc
  • 每个 Buffer 元素对应 0~255,与 Uint8Array 视图共享同一块内存,是 Node 处理二进制、文件、网络包的统一载体。
4.2.2 Buffer.allocUnsafe() - 快速创建

语法:

javascript 复制代码
Buffer.allocUnsafe(size)

特点:

  • 速度快:不进行内存初始化
  • ⚠️ 不安全:可能包含旧数据
  • 适用场景:后续会立即填充数据的场景

使用示例:

javascript 复制代码
// 快速创建未初始化Buffer
const buf5 = Buffer.allocUnsafe(10);
console.log(buf5);
// <Buffer 00 00 00 00 00 00 00 00 00 00>
// 可能包含旧数据,而不是干净的0值

// 安全使用方式:立即覆盖
const safeBuffer = Buffer.allocUnsafe(10);
safeBuffer.fill(0);  // 填充0确保数据安全
console.log(safeBuffer);
// <Buffer 00 00 00 00 00 00 00 00 00 00>

【代码注释】

  • allocUnsafe 只分配内存、不初始化 ,可能读到上次释放留下的字节(脏数据),因此示例里 buf5 有时看起来像全 0,有时却是随机值。
  • 正确用法:分配后立刻 fill(0)write 覆盖全部字节,再交给网络/加密逻辑使用。
  • 大 Buffer(如 1MB)性能测试里 allocUnsafe 通常更快,因为省掉清零;图像解码、流式读文件等「马上写满」的场景常用。
  • 不要用 allocUnsafe 存密码、密钥;安全场景一律 allocalloc + 用后 fill(0) 覆写。

性能对比:

javascript 复制代码
const size = 1024 * 1024;  // 1MB

console.time('alloc');
Buffer.alloc(size);
console.timeEnd('alloc');  // 通常较慢

console.time('allocUnsafe');
Buffer.allocUnsafe(size);
console.timeEnd('allocUnsafe');  // 通常更快
4.2.3 Buffer.from() - 从数据创建

语法:

javascript 复制代码
Buffer.from(string[, encoding])
Buffer.from(buffer)
Buffer.from(array[, byteOffset[, byteLength]])
Buffer.from(arrayBuffer[, byteOffset[, byteLength]])

使用示例:

javascript 复制代码
// 从字符串创建
const buf1 = Buffer.from('Hello 高小乐');
console.log(buf1);
// <Buffer 48 65 6c 6c 6f 20 e9 ab 98 e5 b0 8f e4 b9 90>
// 解释:英文字符占1字节,中文字符在UTF-8中占3字节

// 从数组创建
const buf2 = Buffer.from([1, 2, 3, 255]);
console.log(buf2);
// <Buffer 01 02 03 ff>

// 数组溢出处理
const buf3 = Buffer.from([300, 256, -1]);
console.log(buf3);
// <Buffer 2c 00 ff>
// 解释:300%256=44(0x2c), 256%256=0, -1被转换为255

// 从另一个Buffer创建(拷贝)
const buf4 = Buffer.from(buf1);
console.log(buf4);
// <Buffer 48 65 6c 6c 6f 20 e9 ab 98 e5 b0 8f e4 b9 90>

// 创建视图(共享内存)
const buf5 = Buffer.from(buf1.buffer);
console.log(buf5);
// <Buffer 48 65 6c 6c 6f 20 e9 ab 98 e5 b0 8f e4 b9 90>

【代码注释】

  • Buffer.from('Hello 高小乐')UTF-8 编码:英文 1 字节/字符,中文一般 3 字节/字,故总长度大于 字符串.length(字符个数)。
  • from([1,2,3,255]) 每个数组元素当作无符号字节;300→44256→0-1→255,与下标赋值规则一致。
  • from(buf1)拷贝 一份新 Buffer;from(buf1.buffer) 可能共享底层 ArrayBuffer,改 slice 会影响原数据(见 §4.4.3)。
  • 不传 encoding 时字符串默认 utf8;还可传 hexbase64 等,与 toString 的编码参数成对使用。

4.3 Buffer读写操作

4.3.1 基本读取操作

索引访问:

javascript 复制代码
const buf = Buffer.from('Hello');

// 通过索引读取单个字节
console.log(buf[0]);  // 72 ('H'的ASCII码)
console.log(buf[1]);  // 101 ('e'的ASCII码)

// 获取长度
console.log(buf.length);  // 5

// 遍历Buffer
for (let i = 0; i < buf.length; i++) {
    console.log(`索引${i}: ${buf[i]}`);
}
// 索引0: 72
// 索引1: 101
// 索引2: 108
// 索引3: 108
// 索引4: 111

【代码注释】

  • buf[0] 得到的是字节数值 (如 72),不是字符 'H';显示字符需 String.fromCharCode(buf[0])buf.toString()
  • buf.length字节数 ,不是 Unicode 字符数;含中文的 Buffer 不能直接用 length 当「字数」。
  • 越界访问返回 undefined,不会抛错;写入越界在严格模式下可能被忽略,应始终用 0 <= i < buf.length
  • forEach 回调参数为 (byte, index),便于同时打印十进制与对应 ASCII 可打印字符。

forEach遍历:

javascript 复制代码
const buf = Buffer.from('Hello World');

buf.forEach((byte, index) => {
    console.log(`位置${index}: ${byte} (${String.fromCharCode(byte)})`);
});

toString()方法:

javascript 复制代码
const buf = Buffer.from('Hello');

// 转换为字符串
console.log(buf.toString());  // 'Hello'

// 指定编码
const buf2 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
console.log(buf2.toString('utf-8'));  // 'Hello'

// 部分转换
const buf3 = Buffer.from('Hello World');
console.log(buf3.toString('utf-8', 0, 5));  // 'Hello'

// 十六进制显示
console.log(buf3.toString('hex'));  // '48656c6c6f20576f726c64'

【代码注释】

  • 无参 toString() 默认 UTF-8,把字节按编码解码为 JS 字符串;二进制损坏时可能出现 ``。
  • toString('utf-8', 0, 5) 的第三、四参数是起始/结束字节偏移 (非字符下标),截取英文 5 字节即 'Hello'
  • hex 编码常用于打印密文、校验和、与抓包工具对照;base64 常用于邮件附件、Data URL。

支持的编码格式:

编码 描述 示例
'utf-8' 多字节Unicode编码 '你好'
'utf16le' 小端序UTF-16 用于Windows
'latin1' 单字节编码 ASCII扩展
'base64' Base64编码 'SGVsbG8='
'hex' 十六进制编码 '48656c6c6f'
4.3.2 基本写入操作

索引写入:

javascript 复制代码
const buf = Buffer.alloc(10);

// 单字节写入
buf[0] = 72;   // 'H'
buf[1] = 101;  // 'e'
buf[2] = 108;  // 'l'

console.log(buf.toString());  // 'Hel'

// 数值溢出处理
buf[0] = 300;  // 300 % 256 = 44
console.log(buf[0]);  // 44

// 负数处理
buf[1] = -100;  // 转换为正数
console.log(buf[1]);  // 156

【代码注释】

  • 通过 buf[i] = n 写入时,Node 按无符号 8 位整数 存储:300 → 44300 % 256),-100 → 156(等价于 256 + (-100))。
  • 这与 C 语言 uint8_t 溢出行为一致,处理像素、协议字段时要手动 Math.min(255, Math.max(0, n)) clamp。
  • buf.write('Hello') 从偏移 0 写入 UTF-8 字节,返回实际写入字节数;多字节字符会使「字符数」与「字节数」不一致。
  • write(str, offset, maxLength)maxLength 限制的是字节数 而非字符数,截断中文可能产生乱码,需配合 byteLength 计算。

write()方法:

javascript 复制代码
const buf = Buffer.alloc(20);

// 写入字符串
buf.write('Hello');
console.log(buf.toString());  // 'Hello'

// 指定位置写入
buf.write('World', 5);
console.log(buf.toString());  // 'HelloWorld'

// 限制写入长度
buf.write('1234567890', 10, 5);
console.log(buf.toString());  // 'HelloWorld12345'

// 返回实际写入的字节数
const bytesWritten = buf.write('Test', 15);
console.log(`写入了${bytesWritten}字节`);  // 写入了4字节

【代码注释】

  • 示例中先 write('Hello')write('World', 5) 在同一 Buffer 内拼接,未覆盖的部分仍保留原分配时的 0 或旧值。
  • write('1234567890', 10, 5) 表示从偏移 10 起最多写 5 个字节,多余字符被丢弃,避免越界写。
  • 返回值 bytesWritten 可用于判断是否写满;网络协议组包时常根据该值移动偏移指针。
  • Buffer 总长度在 alloc(20) 时已固定,不能通过 write 自动扩容,需要更大空间应重新 allocconcat

4.4 Buffer高级特性

4.4.1 Buffer溢出处理

概念:

Buffer的每个元素只能表示0-255的数值,超出这个范围的值会发生溢出。

溢出规则:

javascript 复制代码
const buf = Buffer.alloc(5);

// 正常范围
buf[0] = 100;
console.log(buf[0]);  // 100

// 上溢出
buf[1] = 256;   // 256 % 256 = 0
console.log(buf[1]);  // 0

buf[2] = 257;   // 257 % 256 = 1
console.log(buf[2]);  // 1

buf[3] = 365;   // 365 = 0x16D, 取低8位 = 0x6D = 109
console.log(buf[3]);  // 109

// 下溢出
buf[4] = -1;    // 转换为无符号8位整数 = 255
console.log(buf[4]);  // 255

【代码注释】

  • Buffer 每个槽位等价于 Uint8Array 的一个元素,只存 0~255;赋值时自动做模 256 或负数补码转换。
  • 365 的二进制低 8 位是 1090x6D),故 buf[3]===109,不是「截断小数」而是位运算。
  • 图像 RGB、音频采样等若超出 255,必须在业务层 clamp,不能依赖 Buffer 自动纠正。
  • 理解溢出有助于读懂 readUInt8 / writeUInt16BE 等按位读写 API 的语义。

实际应用注意:

javascript 复制代码
// 处理图像数据时的溢出
const pixelBuffer = Buffer.alloc(4);
pixelBuffer[0] = 300;  // 实际存储44
pixelBuffer[1] = -50;  // 实际存储206

// 需要手动处理溢出
const safeValue = Math.min(255, Math.max(0, 300));  // 255
pixelBuffer[2] = safeValue;
4.4.2 中文编码处理

UTF-8编码规则:
#mermaid-svg-Yr5100R943oavF8g{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-Yr5100R943oavF8g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Yr5100R943oavF8g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Yr5100R943oavF8g .error-icon{fill:#552222;}#mermaid-svg-Yr5100R943oavF8g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Yr5100R943oavF8g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Yr5100R943oavF8g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Yr5100R943oavF8g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Yr5100R943oavF8g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Yr5100R943oavF8g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Yr5100R943oavF8g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Yr5100R943oavF8g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Yr5100R943oavF8g .marker.cross{stroke:#333333;}#mermaid-svg-Yr5100R943oavF8g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Yr5100R943oavF8g p{margin:0;}#mermaid-svg-Yr5100R943oavF8g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Yr5100R943oavF8g .cluster-label text{fill:#333;}#mermaid-svg-Yr5100R943oavF8g .cluster-label span{color:#333;}#mermaid-svg-Yr5100R943oavF8g .cluster-label span p{background-color:transparent;}#mermaid-svg-Yr5100R943oavF8g .label text,#mermaid-svg-Yr5100R943oavF8g span{fill:#333;color:#333;}#mermaid-svg-Yr5100R943oavF8g .node rect,#mermaid-svg-Yr5100R943oavF8g .node circle,#mermaid-svg-Yr5100R943oavF8g .node ellipse,#mermaid-svg-Yr5100R943oavF8g .node polygon,#mermaid-svg-Yr5100R943oavF8g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Yr5100R943oavF8g .rough-node .label text,#mermaid-svg-Yr5100R943oavF8g .node .label text,#mermaid-svg-Yr5100R943oavF8g .image-shape .label,#mermaid-svg-Yr5100R943oavF8g .icon-shape .label{text-anchor:middle;}#mermaid-svg-Yr5100R943oavF8g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Yr5100R943oavF8g .rough-node .label,#mermaid-svg-Yr5100R943oavF8g .node .label,#mermaid-svg-Yr5100R943oavF8g .image-shape .label,#mermaid-svg-Yr5100R943oavF8g .icon-shape .label{text-align:center;}#mermaid-svg-Yr5100R943oavF8g .node.clickable{cursor:pointer;}#mermaid-svg-Yr5100R943oavF8g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Yr5100R943oavF8g .arrowheadPath{fill:#333333;}#mermaid-svg-Yr5100R943oavF8g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Yr5100R943oavF8g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Yr5100R943oavF8g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Yr5100R943oavF8g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Yr5100R943oavF8g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Yr5100R943oavF8g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Yr5100R943oavF8g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Yr5100R943oavF8g .cluster text{fill:#333;}#mermaid-svg-Yr5100R943oavF8g .cluster span{color:#333;}#mermaid-svg-Yr5100R943oavF8g 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-Yr5100R943oavF8g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Yr5100R943oavF8g rect.text{fill:none;stroke-width:0;}#mermaid-svg-Yr5100R943oavF8g .icon-shape,#mermaid-svg-Yr5100R943oavF8g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Yr5100R943oavF8g .icon-shape p,#mermaid-svg-Yr5100R943oavF8g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Yr5100R943oavF8g .icon-shape .label rect,#mermaid-svg-Yr5100R943oavF8g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Yr5100R943oavF8g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Yr5100R943oavF8g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Yr5100R943oavF8g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 英文字符
1字节
中文字符
3字节
Emoji表情
4字节

示例代码:

javascript 复制代码
// 创建包含中文的Buffer
const buf = Buffer.from('你好世界');

console.log(buf);
// <Buffer e4 bd a0 e5 a5 bd e4 b8 96 e7 95 8c>

// 字节长度
console.log(buf.length);  // 12字节(4个汉字×3字节)

// 字符长度
console.log('你好世界'.length);  // 4个字符

// 正确转换
console.log(buf.toString('utf-8'));  // '你好世界'

// 错误截取会导致乱码
console.log(buf.slice(0, 4).toString('utf-8'));  // '你�'

// 正确截取(按字符边界)
const validBuf = buf.slice(0, 6);  // 2个汉字=6字节
console.log(validBuf.toString('utf-8'));  // '你好'

【代码注释】

  • '你好世界'.length === 4(字符数),而 Buffer.from(...).length === 12(字节数),二者不可混用。
  • slice(0, 4) 只切 4 个字节,可能落在某个汉字的中间,解码后出现 ``;应按完整 UTF-8 码点边界切(示例中 6 字节 = 2 个汉字)。
  • 生产环境截取摘要可用 safeSubstring:先 toStringsubstringfrom,由字符串语义保证不劈开汉字。
  • 浏览器侧 TextEncoder / TextDecoder 与 Node Buffer 在 UTF-8 下字节一致,便于前后端统一校验长度(见 §8.5 HTML 演示)。

中文处理最佳实践:

javascript 复制代码
// 使用字符串长度计算字节偏移
function safeSubstring(buffer, startChar, endChar) {
    const str = buffer.toString('utf-8');
    const substr = str.substring(startChar, endChar);
    return Buffer.from(substr, 'utf-8');
}

const buf = Buffer.from('你好世界');
const result = safeSubstring(buf, 0, 2);
console.log(result.toString('utf-8'));  // '你好'
4.4.3 Buffer切片与合并

slice()方法:

javascript 复制代码
const buf = Buffer.from('Hello World');

// 切片操作(不复制原始数据)
const slice1 = buf.slice(0, 5);
console.log(slice1.toString());  // 'Hello'

const slice2 = buf.slice(6);
console.log(slice2.toString());  // 'World'

// 负索引
const slice3 = buf.slice(-6);
console.log(slice3.toString());  // 'World'

// 注意:slice返回的是视图,修改会影响原始Buffer
const buf1 = Buffer.from('ABC');
const buf2 = buf1.slice(0, 2);
buf2[0] = 88;  // 'X'
console.log(buf1.toString());  // 'XBC'

concat()方法:

javascript 复制代码
const buf1 = Buffer.from('Hello ');
const buf2 = Buffer.from('World');

// 合并Buffer
const combined = Buffer.concat([buf1, buf2]);
console.log(combined.toString());  // 'Hello World'

// 指定总长度
const totalLength = Buffer.concat([buf1, buf2], 15).length;
console.log(totalLength);  // 15

【代码注释】

  • buf.slice(start, end) 与数组 slice 类似,但返回的 Buffer 共享底层内存 ;改 buf2[0] 会改 buf1,复制需 Buffer.from(buf1)buf1.subarray + 拷贝。
  • 负索引 -6 表示从末尾数第 6 个字节起切,便于取「World」而不算正向偏移。
  • Buffer.concat([buf1, buf2]) 分配内存并拷贝拼接;第二个参数可限制总长度,防止意外超大分配。
  • 大文件处理更推荐 Stream 逐块 concat,而不是一次性读入再 concat 巨型数组。

4.5 实际应用场景

4.5.1 文件上传处理

应用场景:

处理用户上传的图片、文件等二进制数据。

示例代码:

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

const server = http.createServer((req, res) => {
    if (req.method === 'POST' && req.url === '/upload') {
        const chunks = [];

        // 收集数据块
        req.on('data', (chunk) => {
            chunks.push(chunk);
        });

        // 数据接收完成
        req.on('end', () => {
            // 合并Buffer
            const buffer = Buffer.concat(chunks);

            // 保存文件
            const filename = `upload-${Date.now()}.jpg`;
            fs.writeFileSync(filename, buffer);

            res.writeHead(200);
            res.end(`文件已保存: ${filename} (${buffer.length} 字节)`);
        });
    } else {
        res.writeHead(200);
        res.end('请上传文件');
    }
});

server.listen(3000);

【代码注释】

  • HTTP 请求体通过 data 事件分块 到达,每块是 Buffer;不能假设一次读完,必须 chunks.push(chunk) 再在 end 里合并。
  • Buffer.concat(chunks) 按顺序拼接所有块,得到完整文件二进制,再 writeFileSync 落盘;大文件应改用 pipeline + Stream 避免内存暴涨。
  • 未处理 error 事件时,连接中断可能导致内存里一直攒 chunk;生产环境要限制 Content-Length 或大小上限。
  • 这是 Multer、Busboy 等上传中间件的基础原理:底层同样是收集 Buffer/Stream。
4.5.2 网络协议实现

应用场景:

实现自定义协议或解析网络数据包。

示例代码:

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

// 创建UDP服务器
const server = dgram.createSocket('udp4');

server.on('message', (msg, rinfo) => {
    // 解析消息头
    const messageType = msg[0];      // 消息类型
    const sequence = msg.readUInt16BE(1);  // 序列号

    console.log(`收到消息: 类型=${messageType}, 序列号=${sequence}`);
    console.log(`数据内容: ${msg.slice(3).toString()}`);

    // 构建响应Buffer
    const response = Buffer.alloc(10);
    response[0] = 0x01;  // 响应类型
    response.writeUInt16BE(sequence, 1);  // 回显序列号
    response.write('OK', 3);

    // 发送响应
    server.send(response, rinfo.port, rinfo.address);
});

server.bind(41234);

【代码注释】

  • UDP message 回调的第一个参数就是 Buffer;可用 msg[0] 读单字节,用 readUInt16BE(1) 从偏移 1 读大端 16 位整数(高位字节在前)。
  • writeUInt16BE(sequence, 1) 在偏移 1 写入 2 字节序列号,与 readUInt16BE 成对;还有 LE(小端)变体,须与协议文档一致。
  • msg.slice(3).toString() 把从第 3 字节起的载荷当 UTF-8 文本;二进制协议应继续用 readUInt32 等,不要混用 toString
  • TCP/UDP、文件格式(PNG、PDF)普遍按字节偏移解析,Buffer 是 Node 做网络编程的必备工具。
4.5.3 图像处理

应用场景:

图像缩放、裁剪、格式转换等。

示例代码:

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

// 使用Buffer进行图像处理
async function processImage(inputBuffer) {
    try {
        // 缩放图像
        const resizedBuffer = await sharp(inputBuffer)
            .resize(300, 300)
            .toBuffer();

        // 转换格式
        const webpBuffer = await sharp(resizedBuffer)
            .webp()
            .toBuffer();

        return webpBuffer;
    } catch (error) {
        console.error('图像处理失败:', error);
        throw error;
    }
}

// 使用示例
const fs = require('fs');
const inputBuffer = fs.readFileSync('input.jpg');

processImage(inputBuffer)
    .then(outputBuffer => {
        fs.writeFileSync('output.webp', outputBuffer);
        console.log('图像处理完成');
    });

【代码注释】

  • readFileSync 得到的是 Buffer,Sharp 直接在内存里解码/缩放/编码,无需先写成临时文件。
  • toBuffer() 输出仍是 Buffer,可再交给 writeFileSync 或 HTTP res.end(buffer) 返回给客户端。
  • 链式 .resize().webp() 体现管道式处理;错误用 try/catch.catch 捕获,避免损坏数据写入磁盘。
  • 同类库(Jimp、canvas)也以 Buffer 为输入输出,与 §4.5.1 上传场景可串联成「上传 → 压缩 → 存储」。
4.5.4 加密解密操作

应用场景:

数据加密、签名验证、哈希计算。

示例代码:

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

// 生成随机Buffer
const iv = crypto.randomBytes(16);  // 初始化向量
const key = crypto.randomBytes(32); // 密钥

// 加密数据
function encrypt(text) {
    const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    return encrypted;
}

// 解密数据
function decrypt(encryptedText) {
    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
}

// 使用示例
const plaintext = '敏感数据';
const encrypted = encrypt(plaintext);
const decrypted = decrypt(encrypted);

console.log('原文:', plaintext);
console.log('密文:', encrypted);
console.log('解密:', decrypted);

【代码注释】

  • crypto.randomBytes(16) 返回 16 字节 Buffer,常作 AES 的 IV(初始化向量) ;密钥 key 长度须与算法匹配(如 aes-256-cbc 要 32 字节)。
  • cipher.update(text, 'utf8', 'hex') 在「明文字符串 → 密文编码字符串」间转换;final() 必须调用,否则最后一块 padding 丢失导致解密失败。
  • createCipheriv 需要显式传入 keyiv;勿使用已废弃的 createCipher(无 iv 不安全)。
  • 解密时 decipher.update 的输入/输出编码参数与加密对称;生产环境密钥应来自环境变量或 KMS,不要写死在代码里。

5. 内置模块详解

5.0 Node 模块分类与引入方式

Node 中的模块分为三类,使用前都必须先引入

类型 说明 引入示例
内置模块 Node 自带,无需安装 const fs = require('fs')
第三方模块 通过 npm 安装 const express = require('express')
自定义模块 项目内 .js 文件 const util = require('./util')
javascript 复制代码
const path = require('path');   // 内置模块
const fs = require('fs');     // 内置模块
// const lodash = require('lodash');  // 第三方模块(需 npm install)
// const helper = require('./helper'); // 自定义模块

【代码注释】

  • require('fs') 加载的是 Node 内置模块 ,由 C++ 绑定实现,无需 npm install;返回的 fs 对象上挂同步/异步两套 API。
  • 第三方模块(如 lodash)需先 npm install lodash,再 require('lodash'),Node 会按 node_modules 向上查找。
  • 自定义模块 require('./helper') 可省略 .js,实际加载的是文件路径对应的模块,每个文件有独立作用域。
  • ESM 写法为 import fs from 'node:fs'import * as fs from 'fs'node: 前缀显式表示内置模块(Node 14.18+ 推荐)。

5.1 path路径模块

5.1.1 模块概述

概念:

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

引入方式:

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

为什么需要path模块:
#mermaid-svg-NPucw8LF8Hmyb4hA{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-NPucw8LF8Hmyb4hA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NPucw8LF8Hmyb4hA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NPucw8LF8Hmyb4hA .error-icon{fill:#552222;}#mermaid-svg-NPucw8LF8Hmyb4hA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NPucw8LF8Hmyb4hA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NPucw8LF8Hmyb4hA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NPucw8LF8Hmyb4hA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NPucw8LF8Hmyb4hA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NPucw8LF8Hmyb4hA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NPucw8LF8Hmyb4hA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NPucw8LF8Hmyb4hA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NPucw8LF8Hmyb4hA .marker.cross{stroke:#333333;}#mermaid-svg-NPucw8LF8Hmyb4hA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NPucw8LF8Hmyb4hA p{margin:0;}#mermaid-svg-NPucw8LF8Hmyb4hA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NPucw8LF8Hmyb4hA .cluster-label text{fill:#333;}#mermaid-svg-NPucw8LF8Hmyb4hA .cluster-label span{color:#333;}#mermaid-svg-NPucw8LF8Hmyb4hA .cluster-label span p{background-color:transparent;}#mermaid-svg-NPucw8LF8Hmyb4hA .label text,#mermaid-svg-NPucw8LF8Hmyb4hA span{fill:#333;color:#333;}#mermaid-svg-NPucw8LF8Hmyb4hA .node rect,#mermaid-svg-NPucw8LF8Hmyb4hA .node circle,#mermaid-svg-NPucw8LF8Hmyb4hA .node ellipse,#mermaid-svg-NPucw8LF8Hmyb4hA .node polygon,#mermaid-svg-NPucw8LF8Hmyb4hA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NPucw8LF8Hmyb4hA .rough-node .label text,#mermaid-svg-NPucw8LF8Hmyb4hA .node .label text,#mermaid-svg-NPucw8LF8Hmyb4hA .image-shape .label,#mermaid-svg-NPucw8LF8Hmyb4hA .icon-shape .label{text-anchor:middle;}#mermaid-svg-NPucw8LF8Hmyb4hA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NPucw8LF8Hmyb4hA .rough-node .label,#mermaid-svg-NPucw8LF8Hmyb4hA .node .label,#mermaid-svg-NPucw8LF8Hmyb4hA .image-shape .label,#mermaid-svg-NPucw8LF8Hmyb4hA .icon-shape .label{text-align:center;}#mermaid-svg-NPucw8LF8Hmyb4hA .node.clickable{cursor:pointer;}#mermaid-svg-NPucw8LF8Hmyb4hA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NPucw8LF8Hmyb4hA .arrowheadPath{fill:#333333;}#mermaid-svg-NPucw8LF8Hmyb4hA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NPucw8LF8Hmyb4hA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NPucw8LF8Hmyb4hA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NPucw8LF8Hmyb4hA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NPucw8LF8Hmyb4hA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NPucw8LF8Hmyb4hA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NPucw8LF8Hmyb4hA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NPucw8LF8Hmyb4hA .cluster text{fill:#333;}#mermaid-svg-NPucw8LF8Hmyb4hA .cluster span{color:#333;}#mermaid-svg-NPucw8LF8Hmyb4hA 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-NPucw8LF8Hmyb4hA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NPucw8LF8Hmyb4hA rect.text{fill:none;stroke-width:0;}#mermaid-svg-NPucw8LF8Hmyb4hA .icon-shape,#mermaid-svg-NPucw8LF8Hmyb4hA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NPucw8LF8Hmyb4hA .icon-shape p,#mermaid-svg-NPucw8LF8Hmyb4hA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NPucw8LF8Hmyb4hA .icon-shape .label rect,#mermaid-svg-NPucw8LF8Hmyb4hA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NPucw8LF8Hmyb4hA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NPucw8LF8Hmyb4hA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NPucw8LF8Hmyb4hA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 路径拼接问题
硬编码分隔符
跨平台兼容性
相对/绝对路径处理
Windows: \\
Unix: /
自动适配平台
正确解析路径

5.1.2 核心API详解

1. path.join() - 路径拼接

语法:

javascript 复制代码
path.join([...paths])

功能:

使用平台特定的分隔符将路径片段连接在一起,并规范化结果路径。

使用示例:

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

// 基本拼接
const result1 = path.join('src', 'images', 'logo.png');
console.log(result1);
// 输出(Unix):'src/images/logo.png'
// 输出(Windows):'src\\images\\logo.png'

// 使用绝对路径
const result2 = path.join(__dirname, 'data', 'file.txt');
console.log(result2);
// 输出:'/完整路径/project/data/file.txt'

// 处理..
const result3 = path.join('/foo', 'bar', 'baz', '..', 'abc');
console.log(result3);
// 输出:'/foo/bar/abc'

// 处理.
const result4 = path.join('/foo', '.', 'bar');
console.log(result4);
// 输出:'/foo/bar'

【代码注释】

  • path.join('src', 'images', 'logo.png') 用当前系统的分隔符(Windows \,Unix /)拼接,并去掉重复斜杠;不会自动变成绝对路径。
  • join(__dirname, 'data', 'file.txt') 是课堂最常用的写法:无论从哪里执行 node,都能定位到脚本旁边data/file.txt
  • join('/foo', 'bar', 'baz', '..', 'abc') 会解析 .. 为上一级,得到 /foo/bar/abc. 表示当前目录片段,会被规范化掉。
  • 不要用字符串 __dirname + '/data/a.txt',否则在 Windows 上易出现混用 \ / 的问题。

实际应用:

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

// 构建配置文件路径
function getConfigPath() {
    return path.join(__dirname, 'config', 'settings.json');
}

// 构建资源路径
function getResourcePath(resource) {
    return path.join(__dirname, 'public', 'images', resource);
}

// 安全的文件操作
function safeReadFile(filename) {
    const fullPath = path.join(__dirname, 'data', filename);
    return fs.readFileSync(fullPath, 'utf-8');
}

2. path.resolve() - 解析绝对路径

语法:

javascript 复制代码
path.resolve([...paths])

功能:

将路径或路径片段序列解析为绝对路径,从右到左处理路径序列。

使用示例:

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

// 相对于当前工作目录
const result1 = path.resolve('src', 'index.js');
console.log(result1);
// 输出:'/当前工作目录/src/index.js'

// 从当前目录开始
const result2 = path.resolve('.');
console.log(result2);
// 输出:'/当前工作目录'

// 多个路径片段
const result3 = path.resolve('/foo', 'bar', 'baz');
console.log(result3);
// 输出:'/foo/bar/baz'

// 处理..返回上一级
const result4 = path.resolve('/foo/bar', '../baz');
console.log(result4);
// 输出:'/foo/baz'

【代码注释】

  • resolve右向左 扫描:若遇到绝对路径片段(如 /foo),则丢弃其左侧片段;最终得到绝对路径
  • resolve('src', 'index.js') 等价于 cwd + '/src/index.js'(Unix 下),cwd 是启动 node 时的工作目录,不是 __dirname
  • resolve('/foo/bar', '../baz')/foo/baz:中间的 .. 会参与规范化。
  • 需要「相对脚本文件」的绝对路径时,应写 path.resolve(__dirname, 'data', 'a.txt'),不要只写 resolve('./data/a.txt')

path.join() vs path.resolve():

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

// join() 只是简单拼接
const joined = path.join('src', '../data');
console.log(joined);  // 'src/../data'

// resolve() 解析为绝对路径
const resolved = path.resolve('src', '../data');
console.log(resolved);  // '/项目目录/data'

【代码注释】

  • 同一组片段下,join 可能仍含 .. 的相对形式(视片段而定),resolve 则产出基于 cwd 的绝对路径
  • 课堂记忆:join = 拼积木;resolve = 拼完后放到「你站在哪」的地图上定位。

3. path.dirname() - 获取目录路径

语法:

javascript 复制代码
path.dirname(path)

功能:

返回路径中的目录部分。

使用示例:

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

// 获取目录路径
const dir1 = path.dirname('/foo/bar/baz/asdf/quux.html');
console.log(dir1);  // '/foo/bar/baz/asdf'

const dir2 = path.dirname('/foo/bar/baz/asdf/');
console.log(dir2);  // '/foo/bar/baz'

// 使用内置常量
const currentDir = path.dirname(__filename);
console.log(currentDir);  // 当前文件所在目录

【代码注释】

  • dirname('/foo/bar/baz/quux.html')/foo/bar/baz/asdf 的目录部分,不包含文件名。
  • 路径以 / 结尾时,最后一节被视为目录名本身,因此 dirname('/foo/bar/baz/')/foo/bar/baz 的上级。
  • path.dirname(__filename) 等价于 __dirname(在 CommonJS 中),用于日志、配置路径拼接。

4. path.basename() - 获取文件名

语法:

javascript 复制代码
path.basename(path[, ext])

功能:

返回路径中的最后一部分(文件名),可选移除扩展名。

使用示例:

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

// 获取文件名
const name1 = path.basename('/foo/bar/baz/asdf/quux.html');
console.log(name1);  // 'quux.html'

// 移除扩展名
const name2 = path.basename('/foo/bar/baz/asdf/quux.html', '.html');
console.log(name2);  // 'quux'

// 获取当前文件名
const currentFile = path.basename(__filename);
console.log(currentFile);  // 当前文件名.js

// 获取不带扩展名的文件名
const currentFileNoExt = path.basename(__filename, '.js');
console.log(currentFileNoExt);  // 当前文件名

【代码注释】

  • basename('/path/quux.html')'quux.html';第二参数 '.html' 可去掉扩展名得到 'quux'
  • 第二参数必须完全匹配 扩展名字符串(含点),'.HTML''.html' 不同。
  • basename(__filename, '.js') 适合生成日志名、打包入口名,而不带 .js 后缀。

5. path.extname() - 获取扩展名

语法:

javascript 复制代码
path.extname(path)

功能:

返回路径中文件的扩展名,从最后一个.之后到字符串末尾。

使用示例:

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

// 获取扩展名
const ext1 = path.extname('index.html');
console.log(ext1);  // '.html'

const ext2 = path.extname('package.json');
console.log(ext2);  // '.json'

const ext3 = path.extname('app.min.js');
console.log(ext3);  // '.js'(只识别最后一个.)

// 没有扩展名
const ext4 = path.extname('README');
console.log(ext4);  // ''

// 多个点的情况
const ext5 = path.extname('.gitignore');
console.log(ext5);  // ''(以.开头的文件)

【代码注释】

  • extname('app.min.js')'.js',只认最后一个 点,因此 min 不算扩展名;判断类型时要留意 .tar.gz 等双扩展名需自行处理。
  • extname('README')''extname('.gitignore')''(点开头的隐藏文件不视为扩展名)。
  • 上传校验可白名单:['.jpg','.png'].includes(path.extname(file).toLowerCase())

6. path.isAbsolute() - 判断绝对路径

语法:

javascript 复制代码
path.isAbsolute(path)

功能:

判断路径是否为绝对路径。

使用示例:

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

// Unix系统绝对路径
console.log(path.isAbsolute('/foo/bar'));  // true
console.log(path.isAbsolute('foo/bar'));   // false

// Windows系统绝对路径
console.log(path.isAbsolute('C:\\foo\\bar'));  // true
console.log(path.isAbsolute('foo\\bar'));      // false

// 使用内置常量
console.log(path.isAbsolute(__filename));  // true
console.log(path.isAbsolute('./file.js')); // false

【代码注释】

  • Unix/macOS:路径以 / 开头即为绝对路径;./file.js../a 为相对路径。
  • Windows:C:\foo\\server\share 为绝对;单独 foo\bar 为相对。
  • 构建工具里常用:若 entry 已是绝对路径则直接用,否则再 join(context, entry)(见下方 Webpack 风格示例)。
5.1.3 path.parse() 与 path.format() - 路径对象互转

语法:

javascript 复制代码
path.parse(pathString)      // 路径字符串 → 对象
path.format(pathObject)     // 对象 → 路径字符串

path.parse() 返回对象结构:

复制代码
/home/user/project/index.min.js
└─── root: '/'
└─── dir:  '/home/user/project'
└─── base: 'index.min.js'
└─── name: 'index.min'
└─── ext:  '.js'

使用示例:

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

// 1. 解构路径的各个组成部分
const parsed = path.parse('/home/user/project/index.js');
console.log(parsed);
// {
//   root: '/',
//   dir: '/home/user/project',
//   base: 'index.js',
//   ext: '.js',
//   name: 'index'
// }

// 2. 组合路径对象回字符串
const formatted = path.format({
    dir: '/home/user/project',
    name: 'output',
    ext: '.html'
});
console.log(formatted);  // '/home/user/project/output.html'

// 3. 经典技巧:修改扩展名(编译工具高频操作)
function changeExtension(filePath, newExt) {
    const parsed = path.parse(filePath);
    // base 和 name+ext 不能同时生效,设 base 为 undefined 让 name+ext 优先
    return path.format({ ...parsed, ext: newExt, base: undefined });
}

console.log(changeExtension('/src/app.js', '.ts'));           // '/src/app.ts'
console.log(changeExtension('/dist/bundle.min.js', '.css'));  // '/dist/bundle.min.css'

// 4. 批量重命名场景
const files = ['index.jsx', 'utils.jsx', 'api.jsx'];
const renamed = files.map(f => {
    const p = path.parse(f);
    return path.format({ ...p, ext: '.tsx', base: undefined });
});
console.log(renamed);  // ['index.tsx', 'utils.tsx', 'api.tsx']

【代码注释】

  • parse 把路径拆成五个字段:root(根)、dir(目录)、base(文件名含扩展名)、ext(扩展名含点)、name(不含扩展名的文件名)。
  • format 合并时若同时传 basename/extbase 优先 ;要用 name+ext 必须将 base 设为 undefined
  • 修改扩展名(.jsx → .tsx)是编译器和脚手架的高频需求,parse + 展开 + format 比手动字符串拼接更健壮。
  • path.sep:当前系统路径分隔符(Unix '/',Windows '\\');path.delimiter:PATH 环境变量分隔符(Unix ':',Windows ';'),偶尔在跨平台 CLI 工具中用到。
5.1.4 实际应用场景

构建工具路径处理:

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

// Webpack风格的入口文件解析
function resolveEntry(context, entry) {
    if (path.isAbsolute(entry)) {
        return entry;
    }

    if (entry.startsWith('./')) {
        return path.join(context, entry);
    }

    // 尝试解析node_modules
    const nodeModulesPath = path.join(context, 'node_modules', entry);
    if (fs.existsSync(nodeModulesPath)) {
        return nodeModulesPath;
    }

    return path.join(context, entry);
}

// 使用示例
const entryPath = resolveEntry(process.cwd(), './src/index.js');
console.log(entryPath);

【代码注释】

  • isAbsolute(entry) 为真时直接返回,避免把已是 /usr/... 的路径再拼一次 cwd。
  • entry.startsWith('./') 时相对上下文目录 context 解析,对应 Webpack context + entry 语义。
  • 否则尝试 node_modules/entry:模拟 npm 包入口查找;找不到再退回 join(context, entry)
  • 真实 Webpack 还包含 package.json main 字段解析,此处是简化版教学逻辑。

文件上传路径生成:

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

// 生成唯一的文件路径
function generateUploadPath(filename) {
    const ext = path.extname(filename);
    const basename = path.basename(filename, ext);
    const timestamp = Date.now();
    const random = Math.random().toString(36).substring(2, 8);

    // 生成:uploads/2024/01/15/filename-1705314567890-abc123.jpg
    const date = new Date();
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');

    return path.join('uploads', year, month, day, `${basename}-${timestamp}-${random}${ext}`);
}

console.log(generateUploadPath('photo.jpg'));
// 'uploads/2024/01/15/photo-1705314567890-abc123.jpg'

【代码注释】

  • extname / basename(filename, ext) 拆分原名与后缀,避免把扩展名写进 basename 导致重复 .jpg.jpg
  • path.join('uploads', year, month, day, ...) 自动用系统分隔符生成 uploads/2024/01/15/... 层级,便于按日归档与清理。
  • 时间戳 + 随机串降低重名概率;生产环境还可加用户 ID、哈希前缀,并配合 mkdirSync(..., { recursive: true }) 确保目录存在。

5.2 fs文件系统模块

5.2.1 模块概述

概念:

fs(File System)模块提供了文件系统操作的API,支持同步和异步两种方式。

引入方式:

javascript 复制代码
const fs = require('fs');
// 或使用Promise API
const fs = require('fs').promises;

API分类:
#mermaid-svg-eKQ4NGlHB1ubh1ZM{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-eKQ4NGlHB1ubh1ZM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .error-icon{fill:#552222;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .marker.cross{stroke:#333333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eKQ4NGlHB1ubh1ZM p{margin:0;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .cluster-label text{fill:#333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .cluster-label span{color:#333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .cluster-label span p{background-color:transparent;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .label text,#mermaid-svg-eKQ4NGlHB1ubh1ZM span{fill:#333;color:#333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .node rect,#mermaid-svg-eKQ4NGlHB1ubh1ZM .node circle,#mermaid-svg-eKQ4NGlHB1ubh1ZM .node ellipse,#mermaid-svg-eKQ4NGlHB1ubh1ZM .node polygon,#mermaid-svg-eKQ4NGlHB1ubh1ZM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .rough-node .label text,#mermaid-svg-eKQ4NGlHB1ubh1ZM .node .label text,#mermaid-svg-eKQ4NGlHB1ubh1ZM .image-shape .label,#mermaid-svg-eKQ4NGlHB1ubh1ZM .icon-shape .label{text-anchor:middle;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .rough-node .label,#mermaid-svg-eKQ4NGlHB1ubh1ZM .node .label,#mermaid-svg-eKQ4NGlHB1ubh1ZM .image-shape .label,#mermaid-svg-eKQ4NGlHB1ubh1ZM .icon-shape .label{text-align:center;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .node.clickable{cursor:pointer;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .arrowheadPath{fill:#333333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eKQ4NGlHB1ubh1ZM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eKQ4NGlHB1ubh1ZM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eKQ4NGlHB1ubh1ZM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .cluster text{fill:#333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .cluster span{color:#333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM 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-eKQ4NGlHB1ubh1ZM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eKQ4NGlHB1ubh1ZM rect.text{fill:none;stroke-width:0;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .icon-shape,#mermaid-svg-eKQ4NGlHB1ubh1ZM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .icon-shape p,#mermaid-svg-eKQ4NGlHB1ubh1ZM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .icon-shape .label rect,#mermaid-svg-eKQ4NGlHB1ubh1ZM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eKQ4NGlHB1ubh1ZM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eKQ4NGlHB1ubh1ZM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eKQ4NGlHB1ubh1ZM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} fs模块
文件操作
目录操作
文件信息
流式操作
读取
写入
删除
重命名
创建目录
读取目录
删除目录
文件状态
文件权限
文件存在性
读取流
写入流

5.2.2 文件读取操作

1. readFile() - 异步读取文件

语法:

javascript 复制代码
fs.readFile(path[, options], callback)

参数:

  • path:文件路径
  • options:选项对象或编码字符串
  • callback:回调函数 (err, data) => {}

使用示例:

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

// 构建文件路径
const filename = path.join(__dirname, 'data', 'a.txt');

// 方式1:不指定编码,返回Buffer
fs.readFile(filename, (err, data) => {
    if (err) {
        console.log('文件读取失败:', err.errno, err.code);
        return;
    }
    console.log('Buffer数据:', data);
    console.log('转换为字符串:', data.toString());
});

console.log('开始读取...');  // 这行先执行

// 方式2:指定编码,直接返回字符串
fs.readFile(filename, 'utf-8', (err, data) => {
    if (err) {
        console.log('文件读取失败:', err.code);
        return;
    }
    console.log('文件内容:', data);
});

// 方式3:使用options对象
fs.readFile(filename, {
    encoding: 'utf-8',
    flag: 'r'  // 读取模式
}, (err, data) => {
    if (err) {
        console.log('读取失败:', err.message);
        return;
    }
    console.log('文件内容:', data);
});

【代码注释】

  • readFile 把 I/O 交给线程池,主线程继续执行,故 console.log('开始读取...')先于回调里的内容打印------这是理解 Node「非阻塞」的第一课。
  • 不传 encodingdataBuffer ,适合图片/压缩包;传 'utf-8' 则直接得到字符串,适合 .txt.json
  • 回调第一个参数 err 为真表示失败(如 ENOENT 文件不存在),必须 return,否则会对 undefinedtoString 二次报错。
  • path.join(__dirname, 'data', 'a.txt') 保证路径相对脚本;仅写 'data/a.txt' 则相对 cwd,易踩坑。

错误码说明:

错误码 描述 解决方法
ENOENT 文件不存在 检查路径或创建文件
EACCES 权限不足 修改文件权限
EISDIR 路径是目录 使用正确的文件路径

2. readFileSync() - 同步读取文件

语法:

javascript 复制代码
fs.readFileSync(path[, options])

使用示例:

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

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

try {
    // 读取Buffer数据
    // const data = fs.readFileSync(filename);

    // 指定编码读取
    const data = fs.readFileSync(filename, 'utf-8');
    console.log(data);
} catch (error) {
    console.log('文件读取失败:', error.errno, error.code);
}

console.log('开始读取...');  // 在读取完成后执行

【代码注释】

  • readFileSync 在读取完成前阻塞整个事件循环,文件越大、磁盘越慢,停顿越久,因此 Web 服务处理并发请求时慎用。
  • 失败时抛异常而非走回调,必须用 try/catcherror.code === 'ENOENT' 表示路径不存在。
  • 典型场景:启动时读一次 config.json、CLI 工具读用户传入路径、构建脚本读模板------「执行一次、可接受短暂阻塞」。
  • 注释掉的 readFileSync 不指定编码版本返回 Buffer,与异步版行为一致。

同步 vs 异步选择:

javascript 复制代码
// 适合同步的场景
// 1. 启动时读取配置文件
const config = JSON.parse(fs.readFileSync('config.json'));

// 2. 命令行工具
const content = fs.readFileSync(process.argv[2], 'utf-8');
console.log(content);

// 适合异步的场景
// 1. Web服务器处理请求
app.get('/file', (req, res) => {
    fs.readFile('data.json', 'utf-8', (err, data) => {
        if (err) {
            res.status(500).send('Error');
            return;
        }
        res.json(JSON.parse(data));
    });
});

// 2. 大文件处理
fs.readFile('large-file.dat', (err, data) => {
    // 处理大文件
});
5.2.3 文件写入操作

1. writeFile() - 异步写入文件

语法:

javascript 复制代码
fs.writeFile(file, data[, options], callback)

使用示例:

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

// 构建文件路径
const filename = path.resolve('./data/b.txt');

// 要写入的内容
const data01 = '你好小乐' + Math.random() + '\n';

// 方式1:写入字符串
fs.writeFile(filename, data01, err => {
    if (err) {
        console.log('写入失败!', err.errno, err.code);
    } else {
        console.log('写入成功!');
    }
});

// 方式2:写入Buffer
const data02 = Buffer.alloc(20, 100);  // 创建20字节,填充100
fs.writeFile(filename, data02, err => {
    if (err) {
        console.log('写入失败!');
    } else {
        console.log('写入成功!');
    }
});

// 方式3:指定选项
fs.writeFile(filename, data01, {
    encoding: 'utf-8',
    mode: 0o666,  // 文件权限
    flag: 'w'     // 写入模式(覆盖)
}, err => {
    if (err) {
        console.log('写入失败!');
    } else {
        console.log('写入成功!');
    }
});

【代码注释】

  • writeFile 默认 flag: 'w'覆盖 原文件;父目录不存在会报 ENOENT,需先 mkdirSync(..., { recursive: true })
  • 既可写字符串(按 encoding 转字节),也可写 Buffer(二进制);data02 示例写入 20 个值为 100 的字节。
  • 成功时 err === null,失败时打印 err.code(如 EACCES 无权限);不要省略错误分支。
  • path.resolve('./data/b.txt') 相对 cwd 解析,与 join(__dirname, ...) 语义不同,课堂演示需注意当前目录。

写入模式(flag):

模式 描述 文件存在时 文件不存在时
'w' 写入(默认) 截断文件 创建文件
'wx' 排他写入 失败 创建文件
'a' 追加 追加到末尾 创建文件
'ax' 排他追加 失败 创建文件

2. writeFileSync() - 同步写入文件

使用示例:

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

const filename = path.resolve('./data/b.txt');
const data = '同步写入的内容\n';

try {
    fs.writeFileSync(filename, data);
    console.log('写入成功!');
} catch (err) {
    console.log('写入失败!');
}

【代码注释】

  • writeFileSync 一次性把 data 写入磁盘,适合配置生成、导出小报告;写入中事件循环无法处理其他 I/O。
  • 与异步版一样会覆盖目标文件;需要「仅当不存在才写」应使用 flag: 'wx'
  • 捕获异常后可降级提示用户,避免整个 CLI 进程崩溃。

3. appendFile() - 追加写入

使用示例:

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

const filename = './data/log.txt';
const logEntry = `${new Date().toISOString()} - Log message\n`;

// 异步追加
fs.appendFile(filename, logEntry, err => {
    if (err) {
        console.log('写入失败!');
    } else {
        console.log('日志追加成功!');
    }
});

// 同步追加
try {
    fs.appendFileSync(filename, logEntry);
    console.log('日志追加成功!');
} catch (err) {
    console.log('写入失败!');
}

【代码注释】

  • appendFile 等价于 flag: 'a' 的写入:在文件末尾追加,不截断原内容;适合访问日志、操作审计。
  • 文件不存在时自动创建;目录仍须存在,否则同样 ENOENT
  • 高并发下频繁 appendFileSync 会导致大量磁盘 seek,性能差;应批量缓冲后一次写入或使用 WriteStream(见下方性能对比)。

批量写入性能对比:

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

const filename = './data/performance.txt';
const data = '测试数据\n';

// 方式1:多次追加写入(性能较差)
console.time('multiple-writes');
for (let i = 0; i < 10000; i++) {
    fs.appendFileSync(filename, data);
}
console.timeEnd('multiple-writes');

// 方式2:单次写入(性能较好)
console.time('single-write');
let combinedData = '';
for (let i = 0; i < 10000; i++) {
    combinedData += data;
}
fs.writeFileSync(filename, combinedData);
console.timeEnd('single-write');

【代码注释】

  • console.time / timeEnd 测量同步代码耗时;多次 appendFileSync 通常明显慢于一次 writeFileSync
  • 演示说明:日志场景不要在高频循环里同步 append;生产用 Stream 或集中缓冲。
  • 测试前可 unlinkSync 删除旧文件,避免上次运行结果影响体积与耗时。
5.2.4 文件系统操作

1. rename() - 文件重命名和移动

语法:

javascript 复制代码
fs.rename(oldPath, newPath, callback)

使用示例:

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

// 重命名文件
fs.rename('./data/a.txt', './data/a.md', err => {
    if (err) {
        console.log('重命名失败!');
    } else {
        console.log('重命名成功!');
    }
});

// 移动文件到不同目录
fs.rename('./data/a.md', './a.md', err => {
    if (err) {
        console.log('重命名失败!');
    } else {
        console.log('重命名成功!');
    }
});

// 同步版本
try {
    fs.renameSync('./data/b.txt', './b.txt');
    console.log('移动成功!');
} catch (err) {
    console.log('操作失败!');
}

【代码注释】

  • rename 在同一文件系统内通常只改目录项,不复制 数据,故既可改文件名也可「移动」到另一文件夹(./data/a.md./a.md)。
  • 目标路径已存在时行为因系统而异,多数情况下会覆盖;重要文件应先备份。
  • 目标目录必须已存在;rename 不能代替 mkdir
  • 跨设备移动可能失败(EXDEV),需改用 copyFile + unlink

2. unlink() - 删除文件

语法:

javascript 复制代码
fs.unlink(path, callback)

使用示例:

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

// 异步删除
fs.unlink('./a.md', err => {
    if (err) {
        console.log('文件删除失败!');
    } else {
        console.log('文件删除成功!');
    }
});

// 同步删除
try {
    fs.unlinkSync('./b.txt');
    console.log('文件删除成功!');
} catch (err) {
    console.log('文件删除失败:', err.code);
}

【代码注释】

  • unlink 删除文件 硬链接;删除空目录要用 rmdir / rm(Node 16+ fs.rm)。
  • 文件不存在时回调收到 ENOENT;课堂流程 a.txt → a.md → 删除 需按顺序执行,否则后一步失败。
  • 异步版不阻塞;批量删除推荐 fs.promises.unlink + Promise.all

安全的文件删除:

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

function safeDeleteFile(filename) {
    try {
        // 检查文件是否存在
        if (fs.existsSync(filename)) {
            fs.unlinkSync(filename);
            console.log('文件删除成功');
            return true;
        } else {
            console.log('文件不存在');
            return false;
        }
    } catch (err) {
        console.log('删除失败:', err.message);
        return false;
    }
}

// 使用示例
safeDeleteFile('./test.txt');

【代码注释】

  • existsSync 为同步检查,简单直观;Node 官方更推荐 access / stat 异步 API,避免 TOCTOU(检查与删除之间文件被改)竞态。
  • 返回 true/false 便于 CLI 打印「文件不存在」而不是抛栈;生产删除操作建议记录审计日志。
  • unlinkSyncexistsSync 为真后执行,仍可能因权限失败进入 catch

3. 目录操作(mkdir / readdir / stat)

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

const logDir = path.join(__dirname, 'logs');
if (!fs.existsSync(logDir)) {
    fs.mkdirSync(logDir, { recursive: true });
}

fs.readdir(__dirname, (err, files) => {
    if (err) return console.error(err);
    console.log('当前目录文件:', files);
});

const target = path.join(__dirname, 'data', 'a.txt');
fs.stat(target, (err, stats) => {
    if (err) return console.error(err);
    console.log('是文件:', stats.isFile());
    console.log('是目录:', stats.isDirectory());
    console.log('字节大小:', stats.size);
});

【代码注释】

  • mkdirSync(logDir, { recursive: true }):等价 mkdir -p,可一次创建 logs 或多级 a/b/cexistsSync 避免目录已存在时报 EEXIST(部分系统仍可能抛错,可用 try/catch 忽略)。
  • readdir 只列出当前一层 文件名(含子目录名),不含子目录内部文件;要遍历树需递归 readdir + statfs.promises.readdir + withFileTypes: true
  • stat 返回 fs.StatsisFile() / isDirectory() 区分文件与文件夹;size 为字节数,目录的 size 在 Unix 上常为目录项大小而非递归总和。
  • data/a.txtstat 再决定 readFile 还是 readdir,可避免对目录误用 readFile 触发 EISDIR
  • 启动脚本、CLI 可用 existsSync;高并发 Web 服务更推荐异步 access / stat,减少阻塞事件循环。
5.2.5 文件监听:fs.watch()

应用场景:

  • 开发服务器热重载(nodemon 的核心原理)
  • 配置文件自动更新,无需重启服务
  • 构建工具监听源文件变化并触发编译

语法:

javascript 复制代码
fs.watch(filename[, options][, listener])
// listener: (eventType, filename) => void
// eventType: 'rename' | 'change'

基础示例:监听配置文件热重载

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

const configPath = path.join(__dirname, 'config.json');

// 初始加载配置
let config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
console.log('初始配置:', config);

// 监听配置文件变化
const watcher = fs.watch(configPath, (eventType, filename) => {
    if (eventType === 'change') {
        // 加 100ms 延迟:部分编辑器先写临时文件再重命名,过早读可能得到空文件
        setTimeout(() => {
            try {
                config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
                console.log(`[${new Date().toLocaleTimeString()}] 配置已热重载:`, config);
            } catch (err) {
                console.error('配置 JSON 解析失败,保留旧配置:', err.message);
            }
        }, 100);
    }
});

// 进程退出时关闭监听器,释放文件句柄
process.on('SIGINT', () => {
    watcher.close();
    console.log('\n已停止监听,进程退出');
    process.exit(0);
});

进阶示例:监听整个 src 目录触发编译

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

const srcDir = path.join(__dirname, 'src');
const WATCH_EXTS = new Set(['.js', '.ts', '.jsx', '.tsx']);

// { recursive: true } 递归监听子目录(macOS/Windows 原生支持,Linux 需 chokidar)
const dirWatcher = fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
    if (!filename) return;  // Linux 某些情况 filename 为 null

    const ext = path.extname(filename);
    if (!WATCH_EXTS.has(ext)) return;  // 过滤非源文件

    console.log(`[${eventType}] ${filename} → 触发重新编译`);
    // 实际项目中这里调用 rollup/esbuild/webpack 编译函数
    // runBuild();
});

console.log(`监听目录: ${srcDir}`);

【代码注释】

  • eventType'rename'(新建/删除/改名)或 'change'(内容变更),但不同 OS 行为略有差异 :Linux 改名有时触发两个 rename 事件。
  • filename 在 macOS/Windows 通常有值,Linux 偶尔为 null,始终加 if (!filename) 保护。
  • 延迟 100ms 是防止「写入进行中读文件」的惯用 trick;更稳健的方案是对变化事件做去抖(debounce)。
  • { recursive: true } 在 Linux 不受原生支持,推荐跨平台方案用 chokidar,它封装了各平台差异和防抖逻辑。
  • watcher.close() 必须调用,否则监听器会阻止 Node 进程自然退出(事件循环保持活跃)。

fs.watchFile() 对比:

特性 fs.watch fs.watchFile
底层机制 OS 原生 inotify/FSEvents/kqueue 轮询(定时 stat
响应速度 快(毫秒级) 慢(默认 5000ms 轮询)
跨平台 有差异(推荐 chokidar 包装) 一致(但 CPU 开销高)
适用场景 开发服务器、构建工具 网络文件系统(NFS)
5.2.6 实际应用场景

1. 日志系统实现

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

class Logger {
    constructor(logDir = './logs') {
        this.logDir = logDir;
        this.logFile = path.join(logDir, `${this.getDateString()}.log`);
        this.ensureLogDir();
    }

    getDateString() {
        const now = new Date();
        return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
    }

    ensureLogDir() {
        if (!fs.existsSync(this.logDir)) {
            fs.mkdirSync(this.logDir, { recursive: true });
        }
    }

    log(message, level = 'INFO') {
        const timestamp = new Date().toISOString();
        const logEntry = `[${timestamp}] [${level}] ${message}\n`;

        // 异步写入日志
        fs.appendFile(this.logFile, logEntry, err => {
            if (err) {
                console.error('日志写入失败:', err);
            }
        });

        // 同时输出到控制台
        console.log(logEntry.trim());
    }

    info(message) {
        this.log(message, 'INFO');
    }

    error(message) {
        this.log(message, 'ERROR');
    }

    warn(message) {
        this.log(message, 'WARN');
    }
}

// 使用示例
const logger = new Logger();
logger.info('应用程序启动');
logger.error('发生错误');
logger.warn('警告信息');

【代码注释】

  • 构造函数里 path.join(logDir, YYYY-MM-DD.log) 实现按日分文件,便于归档与清理过期日志。
  • ensureLogDir 在首次写入前创建目录,与 §5.2.4 mkdirSync 课堂案例一致。
  • appendFile 异步追加,不阻塞主线程;高 QPS 场景应换 createWriteStream 或专业库(Winston、pino)。
  • info / error / warn 仅改变 [LEVEL] 标记,生产环境还可输出 JSON 行方便 ELK 采集。

2. 配置文件管理

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

class ConfigManager {
    constructor(configDir = './config') {
        this.configDir = configDir;
        this.cache = {};
    }

    loadConfig(filename) {
        const configPath = path.join(this.configDir, filename);

        // 检查缓存
        if (this.cache[filename]) {
            return this.cache[filename];
        }

        try {
            // 读取配置文件
            const content = fs.readFileSync(configPath, 'utf-8');
            const config = JSON.parse(content);

            // 缓存配置
            this.cache[filename] = config;

            return config;
        } catch (err) {
            if (err.code === 'ENOENT') {
                console.log(`配置文件不存在:${filename}`);
                return {};
            } else if (err instanceof SyntaxError) {
                console.log(`配置文件格式错误:${filename}`);
                return {};
            } else {
                console.log(`读取配置失败:${err.message}`);
                return {};
            }
        }
    }

    saveConfig(filename, config) {
        const configPath = path.join(this.configDir, filename);

        try {
            // 确保目录存在
            if (!fs.existsSync(this.configDir)) {
                fs.mkdirSync(this.configDir, { recursive: true });
            }

            // 写入配置文件
            const content = JSON.stringify(config, null, 2);
            fs.writeFileSync(configPath, content);

            // 更新缓存
            this.cache[filename] = config;

            return true;
        } catch (err) {
            console.log(`保存配置失败:${err.message}`);
            return false;
        }
    }

    reloadConfig(filename) {
        delete this.cache[filename];
        return this.loadConfig(filename);
    }
}

// 使用示例
const configManager = new ConfigManager();

// 加载配置
const dbConfig = configManager.loadConfig('database.json');
console.log('数据库配置:', dbConfig);

// 修改配置
dbConfig.host = 'localhost';
dbConfig.port = 3306;

// 保存配置
configManager.saveConfig('database.json', dbConfig);

【代码注释】

  • cache 避免重复读盘:同一 filename 第二次 loadConfig 直接返回内存对象,修改后需 saveConfigreloadConfig 才与磁盘一致。
  • ENOENT 返回空对象 {},适合「首次运行无配置文件」;SyntaxError 表示 JSON 损坏,应提示用户修复而非崩溃。
  • saveConfigJSON.stringify(config, null, 2) 美化缩进,便于人工编辑;writeFileSync 适合低频保存设置。
  • reloadConfig 删除缓存项后重新 readFileSync,适合热更新配置(仍阻塞,大文件建议 fs.watch + 异步读)。

6. 最佳实践与性能优化

6.1 异步编程最佳实践

1. 优先使用异步API

javascript 复制代码
// ❌ 不推荐:阻塞文件读取
const data = fs.readFileSync('large-file.txt');
processFile(data);

// ✅ 推荐:非阻塞文件读取
fs.readFile('large-file.txt', (err, data) => {
    if (err) {
        console.error('读取失败');
        return;
    }
    processFile(data);
});

2. 使用Promise API

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

// async/await语法
async function processFile() {
    try {
        const data = await fs.readFile('data.txt', 'utf-8');
        console.log(data);
    } catch (err) {
        console.error('处理失败:', err);
    }
}

// 使用示例
processFile();

【代码注释】

  • require('fs').promises 返回与回调版同名方法(readFilewriteFile 等),但返回 Promise ,可用 await 链式组合,避免回调地狱。
  • async functionawait fs.readFile 在失败时抛异常,由外层 try/catch 统一捕获,等价于回调里判断 if (err)
  • Node 10+ 原生支持;旧项目也可用 util.promisify(fs.readFile) 手动包装。

6.2 文件操作性能优化

1. 大文件流式处理

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

// ❌ 不推荐:一次性读取大文件
fs.readFile('large-file.txt', (err, data) => {
    // 内存占用高
});

// ✅ 推荐:使用流式处理
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('output.txt');

readStream.pipe(writeStream);

readStream.on('end', () => {
    console.log('文件处理完成');
});

【代码注释】

  • createReadStream 按块(默认 64KB)读入,内存占用近似常量;readFile 把整个文件载入堆,GB 级文件可能导致 OOM。
  • pipe 把可读流接到可写流,自动处理背压(backpressure);复制大文件、视频转码、日志归档的标准写法。
  • 应监听 error 事件(readStream.on('error', ...)),否则磁盘错误可能静默失败。

2. 批量操作优化

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

// ❌ 不推荐:多次单独写入
for (let i = 0; i < 1000; i++) {
    fs.appendFileSync('log.txt', `Log entry ${i}\n`);
}

// ✅ 推荐:批量写入
const logs = [];
for (let i = 0; i < 1000; i++) {
    logs.push(`Log entry ${i}\n`);
}
fs.writeFileSync('log.txt', logs.join(''));

【代码注释】

  • 循环 1000 次 appendFileSync 会打开/关闭文件或移动文件指针 1000 次,磁盘 I/O 次数多,与 §5.2.3 中 10000 次性能对比同理。
  • 先在内存用数组 push 拼接,再一次 writeFileSync 只需一次系统调用写入(日志极大时仍应使用 Stream)。
  • 若必须追加且高吞吐,使用 fs.createWriteStream({ flags: 'a' }) 保持文件句柄打开。

6.3 错误处理策略

1. 统一错误处理

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

function safeFileOperation(operation, ...args) {
    try {
        return operation(...args);
    } catch (err) {
        console.error('操作失败:', err.message);

        // 根据错误类型处理
        switch (err.code) {
            case 'ENOENT':
                console.log('文件不存在');
                break;
            case 'EACCES':
                console.log('权限不足');
                break;
            default:
                console.log('未知错误');
        }
        return null;
    }
}

// 使用示例
const data = safeFileOperation(fs.readFileSync, 'config.json');

2. 错误恢复

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

function readConfigWithFallback(filename, defaultConfig) {
    try {
        const content = fs.readFileSync(filename, 'utf-8');
        return JSON.parse(content);
    } catch (err) {
        console.log('使用默认配置');
        return defaultConfig;
    }
}

// 使用示例
const config = readConfigWithFallback('config.json', {
    port: 3000,
    host: 'localhost'
});

6.4 Stream 流式处理

核心理念:

Stream(流)是 Node.js 处理大数据的核心机制。不把整个文件载入内存,而是以固定大小的**块(chunk)**分批处理,内存占用近似恒定。

四种流类型:

类型 说明 典型例子
Readable 可读流 fs.createReadStream、HTTP 请求体
Writable 可写流 fs.createWriteStream、HTTP 响应
Duplex 双向流 TCP socket、WebSocket
Transform 转换流(边读边写边改) zlib.createGzip(压缩)、加密流

1. 基础:文件复制

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

// ❌ 一次性读入内存------1GB 文件会暂用 1GB+ 内存
// const buf = fs.readFileSync('huge.mp4');
// fs.writeFileSync('copy.mp4', buf);

// ✅ 流式复制------内存占用约等于 chunk size(默认 64KB)
const src = fs.createReadStream('huge.mp4');
const dst = fs.createWriteStream('copy.mp4');

src.pipe(dst);  // pipe 自动处理背压(backpressure)

dst.on('finish', () => console.log('复制完成'));
src.on('error', (err) => console.error('读取失败:', err));
dst.on('error', (err) => console.error('写入失败:', err));

2. 进阶:pipeline ------ 多流串联 + 自动错误处理

javascript 复制代码
const fs = require('fs');
const zlib = require('zlib');
const { pipeline } = require('stream');

// 读取 → 压缩 → 写入(三流串联)
// pipeline 任一流出错,自动销毁其余流,避免内存泄漏
pipeline(
    fs.createReadStream('server.log'),
    zlib.createGzip(),                          // Transform 流:gzip 压缩
    fs.createWriteStream('server.log.gz'),
    (err) => {
        if (err) console.error('压缩失败:', err);
        else console.log('server.log → server.log.gz 压缩完成');
    }
);

3. async/await 写法(Node 15+)

javascript 复制代码
const { pipeline } = require('stream/promises');
const fs = require('fs');
const zlib = require('zlib');

async function compressFile(input, output) {
    await pipeline(
        fs.createReadStream(input),
        zlib.createGzip(),
        fs.createWriteStream(output)
    );
    console.log(`${input} → ${output} 压缩完成`);
}

compressFile('access.log', 'access.log.gz').catch(console.error);

4. 逐行读取大日志文件(readline + for await)

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

async function analyzeLog(logPath) {
    const fileStream = fs.createReadStream(logPath);

    // readline 接口按行分割,不需要一次性载入整个文件
    const rl = readline.createInterface({
        input: fileStream,
        crlfDelay: Infinity  // 兼容 Windows \r\n 换行
    });

    let lineCount = 0;
    let errorCount = 0;

    for await (const line of rl) {
        lineCount++;
        if (line.includes('[ERROR]')) {
            errorCount++;
            console.log(`第 ${lineCount} 行: ${line}`);
        }
    }

    console.log(`\n共 ${lineCount} 行,发现 ${errorCount} 个错误`);
}

analyzeLog('/var/log/app.log').catch(console.error);

5. HTTP 服务中的流式响应

javascript 复制代码
const http = require('http');
const fs = require('fs');
const { pipeline } = require('stream');

const server = http.createServer((req, res) => {
    if (req.url === '/video') {
        const filePath = './assets/demo.mp4';
        const stat = fs.statSync(filePath);

        res.writeHead(200, {
            'Content-Type': 'video/mp4',
            'Content-Length': stat.size
        });

        // 直接 pipe 到响应,边读边发,不占用大量服务器内存
        pipeline(fs.createReadStream(filePath), res, (err) => {
            if (err) console.error('视频传输中断:', err.message);
        });
    }
});

server.listen(3000, () => console.log('视频服务启动: http://localhost:3000/video'));

【代码注释】

  • 背压(backpressure) :当写入速度慢于读取速度时,pipe/pipeline 自动暂停可读流,防止内存无限堆积------这是手动 readFile 无法做到的。
  • pipelinepipe 链更安全:任一环节出错,所有流都会被正确销毁;pipe 链在错误时需要手动监听每个流的 error 事件。
  • readline + for await...of 是分析 GB 级日志的惯用模式,内存仅保留当前行,不受文件大小影响。
  • HTTP 流式响应(视频/大文件下载)可实现「边读边发」,首字节响应时间更快,用户体验更好。
  • zlib.createGzip() 是 Transform 流,插入 pipeline 中间无需缓冲整个文件,是后端日志归档的常见方案。

7. 总结与进阶学习路径

7.1 核心知识点总结

#mermaid-svg-9on5j5MJqQzHwF9q{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-9on5j5MJqQzHwF9q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9on5j5MJqQzHwF9q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9on5j5MJqQzHwF9q .error-icon{fill:#552222;}#mermaid-svg-9on5j5MJqQzHwF9q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9on5j5MJqQzHwF9q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9on5j5MJqQzHwF9q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9on5j5MJqQzHwF9q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9on5j5MJqQzHwF9q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9on5j5MJqQzHwF9q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9on5j5MJqQzHwF9q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9on5j5MJqQzHwF9q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9on5j5MJqQzHwF9q .marker.cross{stroke:#333333;}#mermaid-svg-9on5j5MJqQzHwF9q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9on5j5MJqQzHwF9q p{margin:0;}#mermaid-svg-9on5j5MJqQzHwF9q .edge{stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .section--1 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section--1 path,#mermaid-svg-9on5j5MJqQzHwF9q .section--1 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section--1 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section--1 text{fill:#ffffff;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth--1{stroke-width:17;}#mermaid-svg-9on5j5MJqQzHwF9q .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-0 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-0 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-0 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-0 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-0 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-0{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-0{stroke-width:14;}#mermaid-svg-9on5j5MJqQzHwF9q .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-1 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-1 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-1 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-1 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-1 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-1{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-1{stroke-width:11;}#mermaid-svg-9on5j5MJqQzHwF9q .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-2 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-2 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-2 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-2 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-2 text{fill:#ffffff;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-2{stroke-width:8;}#mermaid-svg-9on5j5MJqQzHwF9q .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-3 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-3 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-3 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-3 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-3 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-3{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-3{stroke-width:5;}#mermaid-svg-9on5j5MJqQzHwF9q .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-4 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-4 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-4 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-4 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-4 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-4{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-4{stroke-width:2;}#mermaid-svg-9on5j5MJqQzHwF9q .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-5 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-5 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-5 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-5 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-5 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-5{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-5{stroke-width:-1;}#mermaid-svg-9on5j5MJqQzHwF9q .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-6 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-6 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-6 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-6 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-6 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-6{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-6{stroke-width:-4;}#mermaid-svg-9on5j5MJqQzHwF9q .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-7 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-7 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-7 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-7 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-7 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-7{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-7{stroke-width:-7;}#mermaid-svg-9on5j5MJqQzHwF9q .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-8 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-8 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-8 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-8 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-8 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-8{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-8{stroke-width:-10;}#mermaid-svg-9on5j5MJqQzHwF9q .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-9 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-9 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-9 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-9 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-9 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-9{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-9{stroke-width:-13;}#mermaid-svg-9on5j5MJqQzHwF9q .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-10 rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-10 path,#mermaid-svg-9on5j5MJqQzHwF9q .section-10 circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-10 polygon,#mermaid-svg-9on5j5MJqQzHwF9q .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-10 text{fill:black;}#mermaid-svg-9on5j5MJqQzHwF9q .node-icon-10{font-size:40px;color:black;}#mermaid-svg-9on5j5MJqQzHwF9q .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .edge-depth-10{stroke-width:-16;}#mermaid-svg-9on5j5MJqQzHwF9q .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled,#mermaid-svg-9on5j5MJqQzHwF9q .disabled circle,#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:lightgray;}#mermaid-svg-9on5j5MJqQzHwF9q .disabled text{fill:#efefef;}#mermaid-svg-9on5j5MJqQzHwF9q .section-root rect,#mermaid-svg-9on5j5MJqQzHwF9q .section-root path,#mermaid-svg-9on5j5MJqQzHwF9q .section-root circle,#mermaid-svg-9on5j5MJqQzHwF9q .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-9on5j5MJqQzHwF9q .section-root text{fill:#ffffff;}#mermaid-svg-9on5j5MJqQzHwF9q .section-root span{color:#ffffff;}#mermaid-svg-9on5j5MJqQzHwF9q .section-2 span{color:#ffffff;}#mermaid-svg-9on5j5MJqQzHwF9q .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-9on5j5MJqQzHwF9q .edge{fill:none;}#mermaid-svg-9on5j5MJqQzHwF9q .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-9on5j5MJqQzHwF9q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Node.js核心
Node.js基础
运行环境
事件驱动
非阻塞I/O
Buffer操作
创建Buffer
读写操作
编码处理
path模块
路径拼接
路径解析
跨平台兼容
fs模块
文件读写
异步操作
错误处理

7.2 学习路径建议

初学者路径:

  1. Node.js基础 → 理解运行环境和基本概念
  2. 模块系统 → 掌握require和模块导出
  3. 异步编程 → 学习回调和Promise
  4. 文件操作 → fs模块的基础使用
  5. 项目实践 → 构建简单的CLI工具

进阶路径:

  1. Stream API → 流式数据处理
  2. HTTP模块 → 构建Web服务器
  3. Express框架 → Web应用开发
  4. 数据库集成 → MongoDB、MySQL
  5. 测试部署 → 单元测试和云部署

专业方向:

  • 后端开发:Express/Koa、REST API、微服务
  • 前端工程化:Webpack、Vite、构建工具
  • 全栈开发:Next.js、Nuxt.js、SSR
  • 桌面应用:Electron、Tauri
  • DevOps:CI/CD、容器化、自动化

7.3 常见问题解答

Q1: 什么时候使用同步API,什么时候使用异步API?

A: 一般情况下优先使用异步API,以下情况可以使用同步API:

  • 启动脚本中的配置文件读取
  • 命令行工具的一次性操作
  • 小文件的快速读写

Q2: Buffer和字符串的区别是什么?

A: Buffer是二进制数据的固定大小容器,字符串是字符序列。处理二进制数据(图片、文件)时使用Buffer,处理文本时使用字符串。

Q3: path.join()和path.resolve()有什么区别?

A: path.join()简单拼接路径片段,path.resolve()会解析为绝对路径。选择取决于是否需要绝对路径。

Q4: 如何处理大文件?

A: 使用Stream API进行流式处理,避免一次性加载到内存:

javascript 复制代码
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);

Q5: 如何读取环境变量?生产环境如何管理敏感配置?

A: 通过 process.env.变量名 读取,开发环境用 dotenv 包加载 .env 文件,生产环境通过 CI/CD 或容器注入,切勿将 .env 提交到 git:

javascript 复制代码
// 安装:npm install dotenv
require('dotenv').config();                          // 读取 .env 文件
const port = Number(process.env.PORT) || 3000;       // 注意转换类型
const isProduction = process.env.NODE_ENV === 'production';

Q6: CommonJS(require)和 ES Modules(import)有何区别?如何选择?

A: CommonJS 是 Node.js 传统模块系统,require 同步加载;ESM 是 ES 标准,支持静态分析和 Tree-shaking,未来趋势。选择建议:

场景 推荐 理由
新项目 / 纯 Node 服务 ESM(.mjs"type":"module" 标准化,支持 top-level await
旧项目维护 CommonJS 避免迁移成本
npm 库 双格式(CJS + ESM) 兼容性最佳
javascript 复制代码
// ESM 中模拟 __dirname(ESM 没有内置该变量)
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

Q7: 如何监控 Node.js 进程的内存泄漏?

A: 结合 process.memoryUsage() 与性能分析工具:

javascript 复制代码
// 定期打印内存快照,观察是否持续上涨
setInterval(() => {
    const mem = process.memoryUsage();
    const toMB = (bytes) => (bytes / 1024 / 1024).toFixed(1);
    console.log(
        `[内存] 堆已用: ${toMB(mem.heapUsed)} MB | ` +
        `堆总量: ${toMB(mem.heapTotal)} MB | ` +
        `RSS: ${toMB(mem.rss)} MB`
    );
}, 5000);

// 内存持续上涨的常见原因:
// 1. 全局数组/Map 无限 push(忘记清理)
// 2. 事件监听器累积(on 不配套 off)
// 3. 闭包意外持有大对象引用
// 4. Stream 没有正确结束(pipe 缺少 error 处理)

进阶工具:node --inspect + Chrome DevTools 堆快照;clinic.js 可视化火焰图。

Q8: path.join() 传入用户输入会有安全风险吗?

A: 有「路径穿越」漏洞风险!用户传入 ../../etc/passwd 可能突破预期目录:

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

// ❌ 危险:直接拼接用户输入
function dangerousRead(userInput) {
    return fs.readFileSync(path.join('/data/uploads', userInput));
}

// ✅ 安全:校验解析后的路径是否在允许目录内
function safeRead(userInput) {
    const base = '/data/uploads';
    const resolved = path.resolve(base, userInput);
    // 确保解析后路径仍以 base 开头
    if (!resolved.startsWith(base + path.sep) && resolved !== base) {
        throw new Error('禁止访问该路径');
    }
    return fs.readFileSync(resolved, 'utf-8');
}

7.4 实用资源推荐

官方资源:

学习平台:

  • MDN Web Docs
  • GitHub开源项目
  • npm包管理器

开发工具:

  • VSCode + Node.js扩展
  • Postman(API测试)
  • MongoDB Compass(数据库管理)

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

8.1 案例学习路线(按顺序练)

步骤 主题 关键 API 验证方式
脚本运行 node xxx.js 终端输出 0~9
内置常量 __dirname__filename 打印绝对路径
process process.envprocess.argv 读取 PORT 变量和命令行参数
创建 Buffer allocallocUnsafefrom 查看 <Buffer ...>
读写 Buffer [i]forEachtoString 中文 UTF-8 占 3 字节
path joinresolveparseformat Windows/Mac 分隔符正确;修改扩展名
读文件 readFile + utf-8 先打印「开始读取」再出内容
写文件 writeFileappendFileSync 注意批量 append 性能
重命名/删除 renameunlink a.txta.md → 删除
目录 mkdirSyncreaddirstat 区分文件与目录
文件监听 fs.watch 修改 config.json 自动打印新值
Stream createReadStreampipeline 大文件复制,内存恒定

8.2 Buffer 速查

#mermaid-svg-J12YYyFpkUHXkIUV{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-J12YYyFpkUHXkIUV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-J12YYyFpkUHXkIUV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-J12YYyFpkUHXkIUV .error-icon{fill:#552222;}#mermaid-svg-J12YYyFpkUHXkIUV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-J12YYyFpkUHXkIUV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-J12YYyFpkUHXkIUV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-J12YYyFpkUHXkIUV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-J12YYyFpkUHXkIUV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-J12YYyFpkUHXkIUV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-J12YYyFpkUHXkIUV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-J12YYyFpkUHXkIUV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-J12YYyFpkUHXkIUV .marker.cross{stroke:#333333;}#mermaid-svg-J12YYyFpkUHXkIUV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-J12YYyFpkUHXkIUV p{margin:0;}#mermaid-svg-J12YYyFpkUHXkIUV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-J12YYyFpkUHXkIUV .cluster-label text{fill:#333;}#mermaid-svg-J12YYyFpkUHXkIUV .cluster-label span{color:#333;}#mermaid-svg-J12YYyFpkUHXkIUV .cluster-label span p{background-color:transparent;}#mermaid-svg-J12YYyFpkUHXkIUV .label text,#mermaid-svg-J12YYyFpkUHXkIUV span{fill:#333;color:#333;}#mermaid-svg-J12YYyFpkUHXkIUV .node rect,#mermaid-svg-J12YYyFpkUHXkIUV .node circle,#mermaid-svg-J12YYyFpkUHXkIUV .node ellipse,#mermaid-svg-J12YYyFpkUHXkIUV .node polygon,#mermaid-svg-J12YYyFpkUHXkIUV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-J12YYyFpkUHXkIUV .rough-node .label text,#mermaid-svg-J12YYyFpkUHXkIUV .node .label text,#mermaid-svg-J12YYyFpkUHXkIUV .image-shape .label,#mermaid-svg-J12YYyFpkUHXkIUV .icon-shape .label{text-anchor:middle;}#mermaid-svg-J12YYyFpkUHXkIUV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-J12YYyFpkUHXkIUV .rough-node .label,#mermaid-svg-J12YYyFpkUHXkIUV .node .label,#mermaid-svg-J12YYyFpkUHXkIUV .image-shape .label,#mermaid-svg-J12YYyFpkUHXkIUV .icon-shape .label{text-align:center;}#mermaid-svg-J12YYyFpkUHXkIUV .node.clickable{cursor:pointer;}#mermaid-svg-J12YYyFpkUHXkIUV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-J12YYyFpkUHXkIUV .arrowheadPath{fill:#333333;}#mermaid-svg-J12YYyFpkUHXkIUV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-J12YYyFpkUHXkIUV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-J12YYyFpkUHXkIUV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J12YYyFpkUHXkIUV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-J12YYyFpkUHXkIUV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J12YYyFpkUHXkIUV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-J12YYyFpkUHXkIUV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-J12YYyFpkUHXkIUV .cluster text{fill:#333;}#mermaid-svg-J12YYyFpkUHXkIUV .cluster span{color:#333;}#mermaid-svg-J12YYyFpkUHXkIUV 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-J12YYyFpkUHXkIUV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-J12YYyFpkUHXkIUV rect.text{fill:none;stroke-width:0;}#mermaid-svg-J12YYyFpkUHXkIUV .icon-shape,#mermaid-svg-J12YYyFpkUHXkIUV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J12YYyFpkUHXkIUV .icon-shape p,#mermaid-svg-J12YYyFpkUHXkIUV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-J12YYyFpkUHXkIUV .icon-shape .label rect,#mermaid-svg-J12YYyFpkUHXkIUV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J12YYyFpkUHXkIUV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-J12YYyFpkUHXkIUV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-J12YYyFpkUHXkIUV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 固定大小空内存
性能优先且会立刻写满
字符串/数组
需要 Buffer?
数据来源
Buffer.alloc
Buffer.allocUnsafe
Buffer.from
读写 toString / 下标

API 要点
alloc(n, fill) 安全,默认填 0;fill=257 等价模 256
allocUnsafe(n) 快,可能含旧数据
from('中文') UTF-8 中文通常 3 字节/字
buf[i]=365 结果 109(保留低 8 位)

8.3 path 与 fs 对照

需求 path fs
拼相对路径 join(__dirname, 'data', 'a.txt') ---
转绝对路径 resolve('./data/b.txt') ---
拆解路径各部分 parse('/foo/bar/index.js') ---
修改扩展名 format({ ...parse(f), ext:'.ts', base:undefined }) ---
读文本 --- readFile(p, 'utf-8', cb)
读大文件 --- createReadStream(p).pipe(writeStream)
写日志 --- appendFile
监听变化 --- fs.watch(p, cb)
改文件名 --- rename('a.txt','a.md', cb)
读环境变量 --- process.env.PORT

【代码注释】

  • path.join('src', '../data') 得到相对路径字符串 'src/../data'(规范化后可能为 'data'),不含盘符与 cwd。
  • path.resolve('src', '../data') 基于当前 cwd 算出绝对路径,如 /Users/you/project/data
  • 读写在项目内应优先 path.join(__dirname, 'data', 'a.txt');只有明确相对「启动目录」时才用 resolve('./...')
  • 与 fs 对照:join 负责拼路径,readFile/writeFile 负责 I/O,二者常成对出现。

8.4 同步 vs 异步选择(归纳)

场景 推荐 原因
Web 接口读大文件 readFile 异步 不阻塞其他请求
启动读 config.json readFileSync 只执行一次,逻辑简单
循环 1 万次 append 先拼字符串再 writeFile 避免磁盘抖动
删除前检查 existsSync + unlinkSync CLI 工具常见写法

8.5 可运行 HTML 演示页(理解 Buffer 与文本)

将下面保存为 buffer-demo.html,用浏览器打开(与 Node Buffer 概念对应:字符在 UTF-8 下的字节长度)。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>UTF-8 字节长度演示</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 560px; margin: 2rem auto; padding: 0 1rem; }
    input { width: 100%; padding: 8px; margin: 8px 0; }
    pre { background: #1e1e1e; color: #d4d4d4; padding: 12px; border-radius: 8px; }
  </style>
</head>
<body>
  <h1>字符串 UTF-8 字节数</h1>
  <p>在 Node 中可用 <code>Buffer.from(str).length</code> 得到相同结果。</p>
  <input id="text" type="text" value="Hello 世界" />
  <pre id="out"></pre>
  <script>
    const input = document.getElementById('text');
    const out = document.getElementById('out');
    function update() {
      const s = input.value;
      const bytes = new TextEncoder().encode(s);
      out.textContent =
        '字符数: ' + s.length + '\n' +
        'UTF-8 字节数: ' + bytes.length + '\n' +
        '十六进制: ' + [...bytes].map(b => b.toString(16).padStart(2, '0')).join(' ');
    }
    input.addEventListener('input', update);
    update();
  </script>
</body>
</html>

【代码注释】

  • TextEncoder().encode(s) 返回 Uint8Array.length 即 UTF-8 字节数,与 Node 中 Buffer.from(s, 'utf8').length 一致。
  • s.length 是 JavaScript UTF-16 码元个数(多数中文仍为 1),与字节数不同,勿用字符数做截断 Buffer。
  • 十六进制行便于对照 buf.toString('hex') 与网络抓包;emoji 等可能占 4 字节,演示时可输入 😀 观察字节变化。
  • 此页可在浏览器直接打开,无需 Node;理解后再回到 §4 用 Buffer.from 做服务端校验(如限制上传字段长度)。

8.6 常见错误码

code 含义 处理
ENOENT 路径不存在 检查 path.join 是否拼错;先 mkdirSync 确保目录存在
EACCES 无权限 chmod 修改权限,或换有权限目录
EISDIR 把目录当文件读 stat 判断,再用 readdir / readFile
EEXIST 文件/目录已存在 mkdirSync{ recursive: true }writeFileflag:'w'
EXDEV 跨设备移动失败 rename 跨磁盘无效,改用 copyFile + unlink
EMFILE 打开文件句柄过多 关闭不用的 Stream 和 watcher;调大 ulimit -n

8.7 process 快速参考

表达式 典型值 / 用途
process.env.NODE_ENV 'development' | 'production'
process.env.PORT 服务端口(字符串,需 Number() 转换)
process.argv.slice(2) 业务参数数组(去掉 node + 脚本路径)
process.cwd() 执行 node 命令时的工作目录
process.platform 'win32' / 'darwin' / 'linux'
process.version 'v20.11.0'(含 v 前缀)
process.exit(0) 正常退出;非 0 表示异常
process.memoryUsage().heapUsed V8 堆已用字节数

本文详细介绍了 Node.js 的核心概念和实际应用,希望对您的学习和开发工作有所帮助。持续实践和探索是掌握 Node.js 的关键!

相关推荐
之歆1 小时前
Day07_Node.js 深度解析:从模块系统到文件操作全指南
node.js
2301_789015621 小时前
Linux基础开发工具一:软件包管理器、vim编辑器
linux·服务器·c语言·汇编·c++·编辑器·vim
胖胖雕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
Codiggerworld17 小时前
Vim 实战:在 VS Code、JetBrains、终端里玩转 Vim
编辑器·vim·excel
甜味弥漫17 小时前
React 快速入门:从 JSX 到列表渲染
react.js·前端框架·node.js
用户938515635071 天前
从模块化到 Prompt 工程:我用 Node.js + LLM 复刻了传统 NLP 的流程
javascript·人工智能·node.js