一篇面向实战的 Node 入门博客:从 运行环境、REPL、Buffer 到
path/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 - 进程对象)
- [3.1
- [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、__filename、global |
拼路径、CLI 工具 |
| Buffer | alloc / from、读写、溢出 |
文件二进制、网络包 |
| path | join、resolve、extname |
跨平台路径、构建工具 |
| 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 scripts :
node 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
核心价值:
-
全栈开发能力
- 前端工程师可快速转型为全栈工程师
- 统一使用JavaScript语言,降低学习成本
- 代码复用率提升,团队协作效率提高
-
前端工程化基础
- 所有现代前端构建工具(Webpack、Vite、Rollup)都基于Node.js
- 包管理器(npm、yarn、pnpm)为项目依赖管理提供支持
- 前端脚手架工具(Vue CLI、Create React App)都运行在Node环境
-
自动化工具开发
- 构建自动化脚本
- 数据爬虫程序
- CI/CD流程脚本
- 部署工具
-
跨平台应用开发
- 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:修订号
下载地址:
- 官方网站:https://nodejs.org/en/download/
- 中文网站:https://nodejs.cn/download/
- 历史版本:https://nodejs.org/dist/
- 国内镜像(历史版本下载):https://npmmirror.com/mirrors/node/
【代码注释】
- 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 |
自动补全 |
实际应用场景:
- 快速测试代码片段
- 调试和实验API
- 学习和探索Node.js特性
- 原型验证
退出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 时常用yargs、commander解析 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]起才是你在命令行传入的参数(如arg1、arg2)。- 与 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集成终端:
- 在VSCode中打开项目文件夹
- 使用快捷键
Ctrl + ~(或Cmd + ~)打开终端 - 或右键文件夹选择"在集成终端中打开"
优势:
- 无需切换窗口
- 直接在项目目录中操作
- 支持多个终端实例
课堂对照:命令行环境
| 平台 | 可用终端 |
|---|---|
| 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
应用场景:
- 日志记录:记录日志来源文件
- 错误追踪:调试时定位问题文件
- 文件路径处理:构建相对路径
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_ENV、PORT、数据库连接串 |
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、子命令、帮助信息)应使用 commander 或 yargs。 - 与
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:
- 二进制数据处理:处理图片、视频、音频等文件
- 网络通信:TCP/UDP协议传输二进制数据
- 文件操作:读写文件的底层实现
- 性能优化:减少数据拷贝,提升处理效率
实际应用案例:
- 图片处理: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:填充值,默认为0encoding:字符编码
返回值: 新的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 → 1(257 % 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存密码、密钥;安全场景一律alloc或alloc+ 用后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→44、256→0、-1→255,与下标赋值规则一致。from(buf1)会拷贝 一份新 Buffer;from(buf1.buffer)可能共享底层ArrayBuffer,改 slice 会影响原数据(见 §4.4.3)。- 不传
encoding时字符串默认utf8;还可传hex、base64等,与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 → 44(300 % 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自动扩容,需要更大空间应重新alloc或concat。
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 位是109(0x6D),故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:先toString再substring再from,由字符串语义保证不劈开汉字。 - 浏览器侧
TextEncoder/TextDecoder与 NodeBuffer在 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或 HTTPres.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需要显式传入key和iv;勿使用已废弃的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合并时若同时传base和name/ext,base优先 ;要用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解析,对应 Webpackcontext+entry语义。- 否则尝试
node_modules/entry:模拟 npm 包入口查找;找不到再退回join(context, entry)。 - 真实 Webpack 还包含
package.jsonmain字段解析,此处是简化版教学逻辑。
文件上传路径生成:
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「非阻塞」的第一课。- 不传
encoding时data为 Buffer ,适合图片/压缩包;传'utf-8'则直接得到字符串,适合.txt、.json。 - 回调第一个参数
err为真表示失败(如ENOENT文件不存在),必须return,否则会对undefined调toString二次报错。 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/catch;error.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 打印「文件不存在」而不是抛栈;生产删除操作建议记录审计日志。 unlinkSync在existsSync为真后执行,仍可能因权限失败进入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/c;existsSync避免目录已存在时报EEXIST(部分系统仍可能抛错,可用 try/catch 忽略)。readdir只列出当前一层 文件名(含子目录名),不含子目录内部文件;要遍历树需递归readdir+stat或fs.promises.readdir+withFileTypes: true。stat返回fs.Stats:isFile()/isDirectory()区分文件与文件夹;size为字节数,目录的 size 在 Unix 上常为目录项大小而非递归总和。- 对
data/a.txt先stat再决定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.4mkdirSync课堂案例一致。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直接返回内存对象,修改后需saveConfig或reloadConfig才与磁盘一致。ENOENT返回空对象{},适合「首次运行无配置文件」;SyntaxError表示 JSON 损坏,应提示用户修复而非崩溃。saveConfig用JSON.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返回与回调版同名方法(readFile、writeFile等),但返回 Promise ,可用await链式组合,避免回调地狱。async function内await 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无法做到的。 pipeline比pipe链更安全:任一环节出错,所有流都会被正确销毁;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 学习路径建议
初学者路径:
- Node.js基础 → 理解运行环境和基本概念
- 模块系统 → 掌握require和模块导出
- 异步编程 → 学习回调和Promise
- 文件操作 → fs模块的基础使用
- 项目实践 → 构建简单的CLI工具
进阶路径:
- Stream API → 流式数据处理
- HTTP模块 → 构建Web服务器
- Express框架 → Web应用开发
- 数据库集成 → MongoDB、MySQL
- 测试部署 → 单元测试和云部署
专业方向:
- 后端开发: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 实用资源推荐
官方资源:
- Node.js官网:https://nodejs.org/
- 中文文档:https://nodejs.cn/
- API文档:https://nodejs.org/api/
学习平台:
- 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.env、process.argv |
读取 PORT 变量和命令行参数 |
| ④ | 创建 Buffer | alloc、allocUnsafe、from |
查看 <Buffer ...> |
| ⑤ | 读写 Buffer | [i]、forEach、toString |
中文 UTF-8 占 3 字节 |
| ⑥ | path | join、resolve、parse、format |
Windows/Mac 分隔符正确;修改扩展名 |
| ⑦ | 读文件 | readFile + utf-8 |
先打印「开始读取」再出内容 |
| ⑧ | 写文件 | writeFile、appendFileSync |
注意批量 append 性能 |
| ⑨ | 重命名/删除 | rename、unlink |
a.txt → a.md → 删除 |
| ⑩ | 目录 | mkdirSync、readdir、stat |
区分文件与目录 |
| ⑪ | 文件监听 | fs.watch |
修改 config.json 自动打印新值 |
| ⑫ | Stream | createReadStream、pipeline |
大文件复制,内存恒定 |
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 };writeFile 用 flag:'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 的关键!