Node.js 与 NPM 包管理完全指南

一篇面向实战的 Node.js 后端入门博客:从 CommonJS / ESM 模块化NPM 生态与包发布 ,到 HTTP 协议原生 http 服务 的完整知识脉络。文中所有示例均可独立运行,不依赖外部讲义或资源路径。

目录

  • 导读:知识架构与权威参考
  • [1. Node.js 模块系统回顾](#1. Node.js 模块系统回顾)
    • [1.1 CommonJS 模块规范](#1.1 CommonJS 模块规范)
    • [1.2 ES Modules (ESM) 模块规范](#1.2 ES Modules (ESM) 模块规范)
    • [1.3 模块路径与文件路径的区别](#1.3 模块路径与文件路径的区别)
  • [2. NPM 包管理工具](#2. NPM 包管理工具)
    • [2.1 NPM 的作用与核心功能](#2.1 NPM 的作用与核心功能)
    • [2.2 NPM 常用命令详解](#2.2 NPM 常用命令详解)
    • [2.3 package.json 配置文件详解](#2.3 package.json 配置文件详解)
    • [2.4 模块查找机制](#2.4 模块查找机制)
    • [2.5 远程仓库协作工作流程](#2.5 远程仓库协作工作流程)
    • [2.6 配置命令别名](#2.6 配置命令别名)
  • [3. 国内镜像工具](#3. 国内镜像工具)
    • [3.1 cnpm 淘宝镜像](#3.1 cnpm 淘宝镜像)
    • [3.2 yarn 包管理工具](#3.2 yarn 包管理工具)
    • [3.3 npx 命令工具](#3.3 npx 命令工具)
    • [3.4 pnpm 高性能包管理工具](#3.4 pnpm 高性能包管理工具)
  • [4. 发布 NPM 包](#4. 发布 NPM 包)
    • [4.1 发布普通包的步骤](#4.1 发布普通包的步骤)
    • [4.2 发布全局命令工具](#4.2 发布全局命令工具)
  • [5. HTTP 协议详解](#5. HTTP 协议详解)
    • [5.1 HTTP 协议概述](#5.1 HTTP 协议概述)
    • [5.2 请求报文详解](#5.2 请求报文详解)
    • [5.3 响应报文详解](#5.3 响应报文详解)
    • [5.4 URL 统一资源定位符](#5.4 URL 统一资源定位符)
    • [5.5 HTTP 状态码详解](#5.5 HTTP 状态码详解)
  • [6. 创建 Node.js HTTP 服务](#6. 创建 Node.js HTTP 服务)
    • [6.1 创建基础 HTTP 服务](#6.1 创建基础 HTTP 服务)
    • [6.2 获取请求报文信息](#6.2 获取请求报文信息)
    • [6.3 设置响应报文](#6.3 设置响应报文)
  • [7. 实战案例与最佳实践](#7. 实战案例与最佳实践)
    • [7.1 文件大小转换工具](#7.1 文件大小转换工具)
    • [7.2 模块路径实践案例](#7.2 模块路径实践案例)
    • [7.3 核心案例合集(模块 + 发布 CLI)](#7.3 核心案例合集(模块 + 发布 CLI))
    • [7.4 浏览器端 HTTP 演示(可运行 HTML)](#7.4 浏览器端 HTTP 演示(可运行 HTML))
    • [7.5 完整 RESTful API 案例(纯 Node.js)](#7.5 完整 RESTful API 案例(纯 Node.js))
    • [7.6 静态文件服务器(可直接用于前端开发)](#7.6 静态文件服务器(可直接用于前端开发))
  • [8. 核心概念总结与术语解析](#8. 核心概念总结与术语解析)
  • [9. 知识点归纳速查与行业应用场景](#9. 知识点归纳速查与行业应用场景)
    • [9.1 一图总览(复习用)](#9.1 一图总览(复习用))
    • [9.2 模块系统对照表](#9.2 模块系统对照表)
    • [9.3 NPM 命令速查](#9.3 NPM 命令速查)
    • [9.4 HTTP 与 Node 服务速查](#9.4 HTTP 与 Node 服务速查)
    • [9.5 行业应用场景(技术向)](#9.5 行业应用场景(技术向))
    • [9.6 常见坑与对策](#9.6 常见坑与对策)
    • [9.7 学习路径建议](#9.7 学习路径建议)
    • [9.8 安全与质量工具](#9.8 安全与质量工具)
    • [9.9 常见面试题解析](#9.9 常见面试题解析)

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

本文解决什么问题

阶段 你会学到 典型产出
模块化 require / import、路径规则、查找机制 可拆分的工具库、业务模块
包管理 npm / yarn / npx、锁文件、镜像 可协作的前端/全栈工程
发布 npm publishbin 全局命令 团队内部 CLI、开源工具包
HTTP 报文结构、状态码、http 模块 静态服务、简易 API、调试代理

知识脉络(Mermaid)

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

require / module.exports
ES Modules

import / export
模块路径 vs 文件路径
package.json
package-lock / yarn.lock
npm install / ci / scripts
HTTP 报文与状态码
http.createServer

权威文档(建议对照阅读)

主题 官方/权威来源 说明
Node 包与 package.json Node.js --- Modules: Packages "type"exportsimports
ESM 在 Node 中 Node.js --- ECMAScript modules .mjsimport 互操作
CommonJS Node.js --- Modules: CommonJS require 查找算法
NPM CLI npm Docs install、publish、workspaces
语义化版本 SemVer 规范 ^ / ~ 含义
HTTP 方法 MDN --- HTTP 方法 GET/POST/PUT/DELETE
HTTP 首部 MDN --- HTTP 标头 请求头/响应头字段
HTTP 状态码 MDN --- HTTP 状态码 2xx/4xx/5xx 分类

行业中的典型落点(只谈技术)

  • Express / Koa / Fastify :依赖 NPM 安装,通过 requireimport 挂载路由与中间件。
  • Vite / Webpack / RollupdevDependencies 管理构建链;npm run build 产出静态资源。
  • Next.js / Nuxtpackage.jsonscripts 统一 dev / build / start 工作流。
  • 企业私有源 :Verdaccio、cnpm 镜像、.npmrcregistry,解决内网依赖分发。
  • 运维与网关 :Nginx 终止 TLS 后转发到 Node http 或集群进程(PM2、Docker)。

1. Node.js 模块系统回顾

Node.js 采用模块化架构,每个文件都被视为一个独立的模块。Node.js 支持两种模块系统:CommonJS 和 ES Modules。

1.1 CommonJS 模块规范

CommonJS 是 Node.js 默认的模块系统,采用同步加载方式,主要适用于服务端开发。

名词解释:CommonJS

CommonJS 是一个模块化规范,定义了模块的格式和加载机制。Node.js 采用了这个规范,使得 JavaScript 可以像其他编程语言一样进行模块化开发。

1.1.1 模块暴露数据

CommonJS 提供了两种暴露数据的方式:

方式一:使用 module.exports

javascript 复制代码
module.exports = function(x, y) {
    return x + y;
};

module.exports = {
    add: function(x, y) { return x + y; },
    subtract: function(x, y) { return x - y; },
    PI: 3.14159
};

【代码注释】

  • module.exports = fn 时,require('./add') 得到的就是该函数,可直接 add(1, 2)(课堂 01-mod03-mod 同理)。
  • 赋值为对象时,require 得到整个对象,用 const { add } = require(...) 解构。
  • 同一文件只能有一个最终 module.exports;后赋值会覆盖先前的导出。

方式二:使用 exports

javascript 复制代码
exports.add = function(x, y) { 
    return x + y; 
};

exports.subtract = function(x, y) { 
    return x - y; 
};

// exports = function() { ... };  // ❌ 错误:断开与 module.exports 的引用

【代码注释】

  • exportsmodule.exports引用 ,只能 exports.xxx = 追加属性,不能 exports = 新对象
  • 错误赋值后 require 可能拿到初始空对象 {},是课堂高频考点。
  • 最终以 module.exports 指向的值为准;exports 只是写法糖。

重要区别:

  • module.exports 是真正的暴露对象,可以直接赋值
  • exportsmodule.exports 的引用,只能添加属性
  • 最终以 module.exports 的值为准
1.1.2 导入模块

使用 require() 函数导入模块:

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

const add = require('./add.js');
const converter = require('./converbyte');

const express = require('express');

console.log(add(100, 200));

【代码注释】

  • require('fs') / require('path')内置模块,按模块名加载,不走相对路径。
  • require('./add.js')自定义模块 ,必须以 ./../ 开头,相对当前文件 目录解析(与 fs.readFile('./data.txt') 相对 cwd 不同,见 §1.3)。
  • require('express')第三方模块 ,从当前目录向上查找 node_modules/express,需先 npm install express
  • require同步 的:返回 module.exports 的最终值;同一文件多次 require 同一路径只执行一次顶层代码(缓存)。
1.1.3 模块文件扩展名规则

Node.js 支持多种模块文件扩展名,查找顺序如下:
#mermaid-svg-p5c5uILx2V8LNw0I{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-p5c5uILx2V8LNw0I .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-p5c5uILx2V8LNw0I .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-p5c5uILx2V8LNw0I .error-icon{fill:#552222;}#mermaid-svg-p5c5uILx2V8LNw0I .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-p5c5uILx2V8LNw0I .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-p5c5uILx2V8LNw0I .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-p5c5uILx2V8LNw0I .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-p5c5uILx2V8LNw0I .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-p5c5uILx2V8LNw0I .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-p5c5uILx2V8LNw0I .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-p5c5uILx2V8LNw0I .marker{fill:#333333;stroke:#333333;}#mermaid-svg-p5c5uILx2V8LNw0I .marker.cross{stroke:#333333;}#mermaid-svg-p5c5uILx2V8LNw0I svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-p5c5uILx2V8LNw0I p{margin:0;}#mermaid-svg-p5c5uILx2V8LNw0I .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-p5c5uILx2V8LNw0I .cluster-label text{fill:#333;}#mermaid-svg-p5c5uILx2V8LNw0I .cluster-label span{color:#333;}#mermaid-svg-p5c5uILx2V8LNw0I .cluster-label span p{background-color:transparent;}#mermaid-svg-p5c5uILx2V8LNw0I .label text,#mermaid-svg-p5c5uILx2V8LNw0I span{fill:#333;color:#333;}#mermaid-svg-p5c5uILx2V8LNw0I .node rect,#mermaid-svg-p5c5uILx2V8LNw0I .node circle,#mermaid-svg-p5c5uILx2V8LNw0I .node ellipse,#mermaid-svg-p5c5uILx2V8LNw0I .node polygon,#mermaid-svg-p5c5uILx2V8LNw0I .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-p5c5uILx2V8LNw0I .rough-node .label text,#mermaid-svg-p5c5uILx2V8LNw0I .node .label text,#mermaid-svg-p5c5uILx2V8LNw0I .image-shape .label,#mermaid-svg-p5c5uILx2V8LNw0I .icon-shape .label{text-anchor:middle;}#mermaid-svg-p5c5uILx2V8LNw0I .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-p5c5uILx2V8LNw0I .rough-node .label,#mermaid-svg-p5c5uILx2V8LNw0I .node .label,#mermaid-svg-p5c5uILx2V8LNw0I .image-shape .label,#mermaid-svg-p5c5uILx2V8LNw0I .icon-shape .label{text-align:center;}#mermaid-svg-p5c5uILx2V8LNw0I .node.clickable{cursor:pointer;}#mermaid-svg-p5c5uILx2V8LNw0I .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-p5c5uILx2V8LNw0I .arrowheadPath{fill:#333333;}#mermaid-svg-p5c5uILx2V8LNw0I .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-p5c5uILx2V8LNw0I .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-p5c5uILx2V8LNw0I .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-p5c5uILx2V8LNw0I .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-p5c5uILx2V8LNw0I .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-p5c5uILx2V8LNw0I .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-p5c5uILx2V8LNw0I .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-p5c5uILx2V8LNw0I .cluster text{fill:#333;}#mermaid-svg-p5c5uILx2V8LNw0I .cluster span{color:#333;}#mermaid-svg-p5c5uILx2V8LNw0I 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-p5c5uILx2V8LNw0I .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-p5c5uILx2V8LNw0I rect.text{fill:none;stroke-width:0;}#mermaid-svg-p5c5uILx2V8LNw0I .icon-shape,#mermaid-svg-p5c5uILx2V8LNw0I .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-p5c5uILx2V8LNw0I .icon-shape p,#mermaid-svg-p5c5uILx2V8LNw0I .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-p5c5uILx2V8LNw0I .icon-shape .label rect,#mermaid-svg-p5c5uILx2V8LNw0I .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-p5c5uILx2V8LNw0I .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-p5c5uILx2V8LNw0I .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-p5c5uILx2V8LNw0I :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} require&(&'./module&'&)
查找顺序
&'./module.js&'
&'./module.json&'
&'./module.node&'
&'./module/index.js&'
&'./module/index.json&'
&'./module/index.node&'
找到即加载

支持扩展名说明:

  • .js - JavaScript 源代码文件(最常用)
  • .json - JSON 数据文件
  • .node - C/C++ 扩展模块
  • 目录 - 自动查找目录下的 index.js

省略扩展名:

javascript 复制代码
const utils = require('./utils');
const config = require('./config.json');
const pkg = require('./package');

【代码注释】

  • 省略 .js 时 Node 按顺序尝试:module.jsmodule.jsonmodule.nodemodule/index.js 等(见上文 Mermaid)。
  • require('./config.json') 直接解析为 JS 对象,无需 fs.readFile + JSON.parse
  • require('./package') 会加载同目录 package.json;注意变量名勿用保留字 package,示例改为 pkg
  • 目录作为模块时,优先读该目录下 package.jsonmain 字段,否则 index.js

1.2 ES Modules (ESM) 模块规范

ES Modules 是 ECMAScript 标准的模块系统,采用异步加载方式,同时适用于浏览器和服务端。

名词解释:ESM (ECMAScript Modules)

ESM 是 JavaScript 官方标准的模块系统,通过 importexport 关键字实现模块的导入和导出。它是现代 JavaScript 开发推荐的模块化方式。

1.2.1 暴露数据

方式一:暴露单个数据(默认导出)

javascript 复制代码
// 【代码注释】使用 export default 暴露单个数据
export default function(x, y) {
    return x + y;
}

// 【代码注释】也可以导出对象、类、常量等
export default {
    name: 'calculator',
    version: '1.0.0'
};

方式二:暴露多个数据(命名导出)

javascript 复制代码
// 【代码注释】直接在声明时导出
export const add = (x, y) => x + y;
export const subtract = (x, y) => x - y;
export const PI = 3.14159;

// 【代码注释】或者集中导出
const add = (x, y) => x + y;
const subtract = (x, y) => x - y;
const multiply = (x, y) => x * y;

export { add, subtract, multiply };

// 【代码注释】导出时重命名
export { add as addition, subtract as subtraction };
1.2.2 导入模块

导入默认导出:

javascript 复制代码
// 【代码注释】导入默认导出的模块
import calculator from './calculator.js';
import MathUtils from './math-utils.js';

导入命名导出:

javascript 复制代码
// 【代码注释】导入指定的导出
import { add, subtract } from './math.js';

// 【代码注释】导入时重命名
import { add as addition, subtract as subtraction } from './math.js';

// 【代码注释】导入所有导出作为对象
import * as MathUtils from './math.js';
MathUtils.add(1, 2);
MathUtils.subtract(5, 3);

混合导入:

javascript 复制代码
// 【代码注释】同时导入默认导出和命名导出
import Calculator, { add, subtract } from './calculator.js';

【代码注释】

  • 默认导出:import 任意名 from './x.js',一个模块仅一个 export default
  • 命名导出:import { add } 名称须与导出一致,或用 as 重命名;import * as M 得到命名空间对象 M.add()
  • 混合导入:默认 + 命名可写在一行 import Def, { a } from '...'
  • 浏览器端 <script type="module"> 与 Node ESM 语法一致,但 Node 需 .mjs"type":"module"
1.2.3 开启 ESM 模块规则

Node.js 默认使用 CommonJS,需要特殊配置才能使用 ESM:

方式一:使用 .mjs 扩展名

javascript 复制代码
// 【代码注释】将文件扩展名改为 .mjs 自动启用 ESM
// calculator.mjs
export default (x, y) => x + y;

// main.mjs
import calc from './calculator.mjs';

方式二:在 package.json 中配置

json 复制代码
{
    "type": "module"
}
javascript 复制代码
// 【代码注释】配置 "type": "module" 后,.js 文件也使用 ESM
// calculator.js
export default (x, y) => x + y;

// main.js
import calc from './calculator.js';

重要提示:

javascript 复制代码
// 【代码注释】在 ESM 中不能使用 require()
// require('./module.js');  // 错误:require is not defined

// 【代码注释】在 CommonJS 中不能使用 import
// import calc from './calc.js';  // 错误:Unexpected token 'import'

【代码注释】

  • 默认导出 export default:一个模块只能有一个;导入时可随意命名 import calc from './x.js'
  • 命名导出 export const add:可有多个;导入必须同名或用 as 重命名,import * as M 得到命名空间对象。
  • 启用 ESM:.mjs 文件 根目录 package.json"type": "module"(此时 .js 按 ESM 解析,.cjs 仍为 CommonJS)。
  • ESM 中无 require/module.exports;CJS 中不能写顶层 import(除非用动态 import() 或构建工具转译)。
  • 浏览器与 Node 现代项目均推荐 ESM;维护老 Express 项目时仍常见 CJS。

1.3 模块路径与文件路径的区别

理解模块路径和文件路径的区别对于正确使用 Node.js 模块系统至关重要。

核心区别

渲染错误: Mermaid 渲染失败: Parse error on line 8: ...¶ß./utils.js&fl°°39¶ß]] B2 --> B3 -----------------------^ Expecting 'SQE', 'TAGEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'SUBROUTINEEND'

实践案例
javascript 复制代码
// 【代码注释】项目结构:
// project/
// ├── src/
// │   ├── main.js
// │   └── utils/
// │       └── helper.js
// └── data.txt

// 【代码注释】在 src/main.js 中:

// 文件路径(相对命令行目录)
fs.readFile('./data.txt', 'utf-8', (err, data) => {
    // 【代码注释】这里 ./data.txt 相对于命令行所在目录
    // 如果在 project/ 目录运行 node src/main.js
    // 那么实际路径是 project/data.txt
});

// 模块路径(相对于当前文件目录)
const helper = require('./utils/helper.js');
// 【代码注释】这里 ./utils/helper.js 相对于 src/main.js
// 【代码注释】实际路径是 src/utils/helper.js
常见错误示例
javascript 复制代码
// 【代码注释】错误示例:混淆文件路径和模块路径

// 假设在 src/modules/main.js 中
// 项目结构:
// project/
// ├── data/
// │   └── config.json
// └── src/
//     └── modules/
//         └── main.js

// 【代码注释】从 project/ 目录运行:node src/modules/main.js

// ❌ 错误:这会查找 project/data/config.json
fs.readFile('./data/config.json', 'utf-8', callback);

// ✅ 正确:使用 __dirname 获取当前文件目录
const path = require('path');
fs.readFile(
    path.join(__dirname, '../../data/config.json'),
    'utf-8',
    callback
);

// 【代码注释】而对于 require(),始终相对于当前文件
const config = require('../../data/config.json');  // ✅ 正确
核心案例:模块路径 vs 文件路径(完整可运行)

下面三个文件放在同一目录 demo-module-path/ 下,可直接复制运行。

add.js --- CommonJS 导出加法函数:

javascript 复制代码
module.exports = (x, y) => x + y;

【代码注释】

  • module.exports = (x, y) => x + y 导出单个函数require('./add.js') 返回值可直接 add(100, 200)
  • 文件名可写 ./add./add.js,Node 按扩展名查找顺序解析。
  • exports.add = fn 区别:后者 require 得到 { add: fn },需解构或点号调用。

01-read-and-add.js --- 演示「文件路径相对命令行、模块路径相对当前文件」:

javascript 复制代码
const fs = require('fs');
const path = require('path');
const add = require('./add.js');

// 【代码注释】文件路径:相对「执行 node 时所在目录」
fs.readFile('./data.txt', 'utf-8', (err, data) => {
    if (err) {
        console.log('文件读取失败:', err.message);
        return;
    }
    console.log('data.txt 内容:', data);
});

console.log('100 + 200 =', add(100, 200));

【代码注释】

  • 必须在 demo-module-path/ 目录执行 node 01-read-and-add.js,否则 ./data.txt 相对 cwd 会找不到(ENOENT)。
  • require('./add.js') 始终相对本 .js 文件所在目录,与 cwd 无关------这是 §1.3「模块路径 vs 文件路径」的核心对比。
  • fs.readFile('./data.txt') 读的是「你站在哪执行 node」下的 data.txt;require 读的是「脚本旁边」的模块。
  • 先打印文件内容再 add(100,200)readFile 异步,加法结果往往先于文件内容输出。

02-stat-with-converter.js --- 结合自定义模块做容量换算:

javascript 复制代码
const fs = require('fs');
const converByte = require('./converbyte/index.js');

const filename = './sample.bin'; // 【代码注释】换成你本地的任意文件即可

fs.stat(filename, (err, stat) => {
    if (err) {
        console.log('文件读取失败:', err.message);
        return;
    }
    console.log('B:', stat.size);
    console.log('KB:', converByte(stat.size, 1));
    console.log('MB:', converByte(stat.size, 2));
    console.log('GB:', converByte(stat.size, 3));
    console.log('TB:', converByte(stat.size, 4));
});

converbyte/index.js

javascript 复制代码
const coverByte = (bytes, type = 0) => bytes / 1024 ** type;
module.exports = coverByte;

【代码注释】

  • fs.statstat.size 单位为字节(B)converByte(size, type)bytes / 1024 ** type 换算。
  • type=0 原样字节;1 KB;2 MB;3 GB;4 TB(基于 1024 进制,与 Windows 资源管理器一致)。
  • require('./converbyte/index.js') 可省略 index,Node 会解析目录下的 index.js
  • ./sample.bin 换成任意存在文件即可;无文件时 err.message 提示路径错误。

03-use-esm.mjs --- ESM 写法(需 Node 14+):

javascript 复制代码
import * as fs from 'node:fs';
import converByte from './converbyte/index.mjs';

const filename = './sample.bin';

fs.stat(filename, (err, stat) => {
    if (err) {
        console.log('文件读取失败:', err.message);
        return;
    }
    console.log('B:', stat.size);
    console.log('KB:', converByte(stat.size, 1));
});

converbyte/index.mjs

javascript 复制代码
const coverByte = (bytes, type = 0) => bytes / 1024 ** type;
export default coverByte;

【代码注释】

  • import converByte from './converbyte/index.mjs' 为 ESM 默认导入 ,对应 export default coverByte
  • import * as fs from 'node:fs' 使用 node: 前缀显式加载内置模块(Node 14.18+ 推荐写法)。
  • .mjs 扩展名强制按 ESM 解析;或在根 package.json"type": "module" 使 .js 也按 ESM。
  • 同一文件内不要混写顶层 requireimportconverbyte 需同时提供 .js(CJS)与 .mjs(ESM)两套入口时,注意发布字段 exports
bash 复制代码
node 01-read-and-add.js
node 02-stat-with-converter.js
node 03-use-esm.mjs

2. NPM 包管理工具

NPM (Node Package Manager) 是 Node.js 的默认包管理工具,也是全球最大的开源库生态系统。

2.1 NPM 的作用与核心功能

名词解释:NPM (Node Package Manager)

NPM 是 Node.js 的包管理工具,它不仅是命令行工具,也是一个在线的包仓库。开发者可以通过 NPM 发现、共享、使用和构建代码包。

核心功能

#mermaid-svg-ijAXyY75dFjwZ3ah{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-ijAXyY75dFjwZ3ah .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ijAXyY75dFjwZ3ah .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ijAXyY75dFjwZ3ah .error-icon{fill:#552222;}#mermaid-svg-ijAXyY75dFjwZ3ah .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ijAXyY75dFjwZ3ah .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ijAXyY75dFjwZ3ah .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ijAXyY75dFjwZ3ah .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ijAXyY75dFjwZ3ah .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ijAXyY75dFjwZ3ah .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ijAXyY75dFjwZ3ah .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ijAXyY75dFjwZ3ah .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ijAXyY75dFjwZ3ah .marker.cross{stroke:#333333;}#mermaid-svg-ijAXyY75dFjwZ3ah svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ijAXyY75dFjwZ3ah p{margin:0;}#mermaid-svg-ijAXyY75dFjwZ3ah .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ijAXyY75dFjwZ3ah .cluster-label text{fill:#333;}#mermaid-svg-ijAXyY75dFjwZ3ah .cluster-label span{color:#333;}#mermaid-svg-ijAXyY75dFjwZ3ah .cluster-label span p{background-color:transparent;}#mermaid-svg-ijAXyY75dFjwZ3ah .label text,#mermaid-svg-ijAXyY75dFjwZ3ah span{fill:#333;color:#333;}#mermaid-svg-ijAXyY75dFjwZ3ah .node rect,#mermaid-svg-ijAXyY75dFjwZ3ah .node circle,#mermaid-svg-ijAXyY75dFjwZ3ah .node ellipse,#mermaid-svg-ijAXyY75dFjwZ3ah .node polygon,#mermaid-svg-ijAXyY75dFjwZ3ah .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ijAXyY75dFjwZ3ah .rough-node .label text,#mermaid-svg-ijAXyY75dFjwZ3ah .node .label text,#mermaid-svg-ijAXyY75dFjwZ3ah .image-shape .label,#mermaid-svg-ijAXyY75dFjwZ3ah .icon-shape .label{text-anchor:middle;}#mermaid-svg-ijAXyY75dFjwZ3ah .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ijAXyY75dFjwZ3ah .rough-node .label,#mermaid-svg-ijAXyY75dFjwZ3ah .node .label,#mermaid-svg-ijAXyY75dFjwZ3ah .image-shape .label,#mermaid-svg-ijAXyY75dFjwZ3ah .icon-shape .label{text-align:center;}#mermaid-svg-ijAXyY75dFjwZ3ah .node.clickable{cursor:pointer;}#mermaid-svg-ijAXyY75dFjwZ3ah .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ijAXyY75dFjwZ3ah .arrowheadPath{fill:#333333;}#mermaid-svg-ijAXyY75dFjwZ3ah .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ijAXyY75dFjwZ3ah .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ijAXyY75dFjwZ3ah .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ijAXyY75dFjwZ3ah .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ijAXyY75dFjwZ3ah .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ijAXyY75dFjwZ3ah .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ijAXyY75dFjwZ3ah .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ijAXyY75dFjwZ3ah .cluster text{fill:#333;}#mermaid-svg-ijAXyY75dFjwZ3ah .cluster span{color:#333;}#mermaid-svg-ijAXyY75dFjwZ3ah 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-ijAXyY75dFjwZ3ah .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ijAXyY75dFjwZ3ah rect.text{fill:none;stroke-width:0;}#mermaid-svg-ijAXyY75dFjwZ3ah .icon-shape,#mermaid-svg-ijAXyY75dFjwZ3ah .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ijAXyY75dFjwZ3ah .icon-shape p,#mermaid-svg-ijAXyY75dFjwZ3ah .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ijAXyY75dFjwZ3ah .icon-shape .label rect,#mermaid-svg-ijAXyY75dFjwZ3ah .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ijAXyY75dFjwZ3ah .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ijAXyY75dFjwZ3ah .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ijAXyY75dFjwZ3ah :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} NPM 核心功能
包的发现与搜索
包的安装与管理
包的发布与共享
版本控制与依赖管理
npm search 关键词
npm view 包名
npm install 包名
npm update 包名
npm remove 包名
npm publish
npm unpublish
语义化版本控制
依赖关系解析

三大使用场景
  1. 使用第三方包

    bash 复制代码
    # 【命令注释】安装项目依赖包
    npm install express
    npm install lodash
    npm install axios
  2. 安装命令行工具

    bash 复制代码
    # 【命令注释】全局安装命令行工具
    npm install -g nodemon
    npm install -g create-react-app
    npm install -g typescript
  3. 发布自己的包

    bash 复制代码
    # 【命令注释】发布包到 NPM 仓库
    npm publish

2.2 NPM 常用命令详解

2.2.1 版本查看与初始化
bash 复制代码
# 【命令注释】查看 npm 版本
npm -v
# 输出示例:8.19.2

# 【命令注释】查看 Node.js 和 npm 版本信息
node -v
npm -v

# 【命令注释】初始化项目(交互式)
npm init
# 【命令注释】会依次询问:
# - package name: (项目名称)
# - version: (1.0.0)
# - description: (项目描述)
# - entry point: (index.js)
# - test command:
# - git repository:
# - keywords:
# - author:
# - license: (ISC)

# 【命令注释】快速初始化(使用默认配置)
npm init -y
# 或
npm init --yes

# 【命令注释】快速初始化生成的 package.json:
# {
#   "name": "project-name",
#   "version": "1.0.0",
#   "description": "",
#   "main": "index.js",
#   "scripts": {
#     "test": "echo \"Error: no test specified\" && exit 1"
#   },
#   "keywords": [],
#   "author": "",
#   "license": "ISC"
# }

【代码注释】

  • npm init 交互式生成 package.json-y / --yes 全部用默认值,适合课堂快速搭架子。
  • 生成后应检查 name(发布时全局唯一)、mainrequire('你的包名') 的入口)、license
  • npm init 不会安装依赖;装包用 npm install <pkg>,会同时更新 package.jsonpackage-lock.json
  • 已有 package.json 时勿重复 init,直接 npm install 即可。
2.2.2 包的安装
bash 复制代码
# 【命令注释】安装包(添加到 dependencies)
npm install express
# 或简写
npm i express

# 【命令注释】安装多个包
npm install express lodash axios

# 【命令注释】安装指定版本
npm install express@4.18.1
npm install express@4.x        # 安装 4.x.x 最新版本
npm install express@^4.18.0    # 兼容版本

# 【命令注释】安装到开发依赖(添加到 devDependencies)
npm install --save-dev jest
npm install -D typescript
# 【代码注释】开发依赖:只在开发过程中需要的包
# 例如:测试框架、构建工具、类型检查工具等

# 【命令注释】全局安装(主要用于命令行工具)
npm install -g nodemon
npm install --global create-react-app
npm install -g @vue/cli

# 【命令注释】查看全局安装位置
npm root -g
# 输出示例:/usr/local/lib/node_modules

# 【命令注释】查看全局安装的包
npm list -g --depth=0

【代码注释】

  • npm install express:写入 dependencies 并安装到 ./node_modules/express,同时更新 lock 文件。
  • -D / --save-dev:写入 devDependencies,生产部署时 npm ci --omit=dev 可省略(仅装运行依赖)。
  • -g:装到全局 node_modulesnpm root -g 可查路径),用于 CLI 工具nodemontypescript),不会进入当前项目的 package.json(除非加 -g 且未 -D 的误用)。
  • express@^4.18.0^ 允许次版本、补丁升级,安装时由 npm 解析为 lock 中的精确版本
  • npm outdated 对比 Current / Wanted / Latest,升级前建议先看 CHANGELOG 破坏性变更。
2.2.3 包的删除与更新
bash 复制代码
# 【命令注释】卸载已安装的包
npm uninstall express
# 或
npm remove express
# 或
npm r express

# 【命令注释】卸载全局包
npm uninstall -g nodemon

# 【命令注释】更新包到最新版本
npm update express
npm update -g npm

# 【命令注释】检查哪些包可以更新
npm outdated
# 【命令注释】输出示例:
# Package  Current  Wanted  Latest  Location
# express  4.17.1   4.18.2  4.18.2  project
# lodash    4.17.15 4.17.21 4.17.21 project

# 【命令注释】更新 npm 自身到最新版本
npm install -g npm@latest
2.2.4 依赖安装与缓存管理
bash 复制代码
# 【命令注释】根据 package.json 安装所有依赖
npm install
# 或
npm i

# 【命令注释】精确安装 package-lock.json 中的版本
npm ci

# 【命令注释】清除 npm 缓存
npm cache clean --force
# 【代码注释】强制清除缓存,解决安装问题时的常用方法

# 【命令注释】验证缓存
npm cache verify

# 【命令注释】查看缓存内容
npm cache ls

【代码注释】

  • 克隆项目后先 npm install:按 package-lock.json(或 npm-shrinkwrap)装与作者一致的树。
  • npm ci:删 node_modules 后按 lock 精确重装 ,CI/CD 常用;要求 lock 与 package.json 一致,否则失败。
  • npm cache clean --force:清缓存排错「装了一半、checksum 不对」;之后重装会重新拉 tarball。
  • 不要手改 node_modules 后指望 install 自动修复;应改 package.jsoninstallci
2.2.5 信息查询命令
bash 复制代码
# 【命令注释】查看包信息
npm view express
npm info express

# 【命令注释】查看包的特定版本信息
npm view express@4.18.1

# 【命令注释】查看包的依赖关系
npm view express dependencies

# 【命令注释】查看已安装的包
npm list
npm list --depth=0  # 只显示顶层依赖

# 【命令注释】查看全局安装的包
npm list -g --depth=0

# 【命令注释】查看包的文档
npm home express
npm repo express
npm bugs express

2.3 package.json 配置文件详解

名词解释:package.json

package.json 是 Node.js 项目的配置文件,包含了项目的元数据、依赖信息、脚本命令等关键信息。它是 Node.js 项目的身份证。

完整配置示例
json 复制代码
{
  "name": "my-awesome-project",          // 【代码注释】包名(必须唯一)
  "version": "1.0.0",                    // 【代码注释】版本号(遵循语义化版本)
  "description": "An awesome Node.js project",  // 【代码注释】项目描述
  "main": "src/index.js",                // 【代码注释】入口文件
  "type": "module",                      // 【代码注释】模块类型(commonjs 或 module)
  
  "scripts": {                           // 【代码注释】可执行脚本
    "start": "node src/index.js",        // 【代码注释】启动命令
    "dev": "nodemon src/index.js",       // 【代码注释】开发模式
    "test": "jest",                      // 【代码注释】运行测试
    "build": "webpack --mode production",// 【代码注释】构建命令
    "lint": "eslint src/**/*.js"         // 【代码注释】代码检查
  },
  
  "keywords": [                          // 【代码注释】关键词(便于搜索)
    "nodejs",
    "web-framework",
    "api"
  ],
  
  "author": "Your Name <your.email@example.com>",  // 【代码注释】作者信息
  "license": "MIT",                    // 【代码注释】开源协议
  
  "dependencies": {                    // 【代码注释】生产环境依赖
    "express": "^4.18.2",              // 【代码注释】Web 框架
    "mongoose": "^7.0.0",              // 【代码注释】MongoDB ODM
    "axios": "^1.4.0",                 // 【代码注释】HTTP 客户端
    "dotenv": "^16.0.3"                // 【代码注释】环境变量管理
  },
  
  "devDependencies": {                 // 【代码注释】开发环境依赖
    "nodemon": "^2.0.22",              // 【代码注释】自动重启工具
    "jest": "^29.5.0",                 // 【代码注释】测试框架
    "eslint": "^8.40.0",               // 【代码注释】代码检查工具
    "webpack": "^5.85.0",              // 【代码注释】打包工具
    "typescript": "^5.0.4"             // 【代码注释】TypeScript 编译器
  },
  
  "engines": {                         // 【代码注释】运行环境要求
    "node": ">=14.0.0",
    "npm": ">=6.0.0"
  },
  
  "config": {                          // 【代码注释】自定义配置
    "port": 3000
  },
  
  "repository": {                      // 【代码注释】代码仓库信息
    "type": "git",
    "url": "https://github.com/username/my-awesome-project.git"
  },
  
  "bugs": {                            // 【代码注释】问题反馈地址
    "url": "https://github.com/username/my-awesome-project/issues"
  },
  
  "homepage": "https://github.com/username/my-awesome-project#readme",
  
  "bin": {                             // 【代码注释】命令行工具配置
    "my-cli": "./bin/cli.js"
  },
  
  "files": [                           // 【代码注释】发布包含的文件
    "src",
    "README.md",
    "LICENSE"
  ]
}

【代码注释】

  • name:发布到 npm 时全局唯一 ;作用域包写 @scope/pkg
  • main:别人 require('你的包名') 时加载的文件;与 exports 字段(Node 12+)可并存,现代包优先配 exports
  • type: "module":根目录 .jsESM 解析;CommonJS 工具链项目勿随意开启。
  • dependencies:运行时需要(express);devDependencies:仅开发/构建需要(jestwebpack),生产 npm ci --omit=dev 可不装。
  • scripts:通过 npm run 执行;bin 注册全局命令,见 §4.2 / §7.3。
  • files / .npmignore:控制 npm publish 上传哪些文件;未列出的可能被忽略(也受 .gitignore 影响,npm 7+ 行为以文档为准)。
  • engines:提示 Node/npm 最低版本,不强制;可用 engines.node 配合 only-allow 锁包管理器。
版本号规则详解

#mermaid-svg-CgMgw0MTJ8u0ruor{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-CgMgw0MTJ8u0ruor .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CgMgw0MTJ8u0ruor .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CgMgw0MTJ8u0ruor .error-icon{fill:#552222;}#mermaid-svg-CgMgw0MTJ8u0ruor .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CgMgw0MTJ8u0ruor .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CgMgw0MTJ8u0ruor .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CgMgw0MTJ8u0ruor .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CgMgw0MTJ8u0ruor .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CgMgw0MTJ8u0ruor .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CgMgw0MTJ8u0ruor .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CgMgw0MTJ8u0ruor .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CgMgw0MTJ8u0ruor .marker.cross{stroke:#333333;}#mermaid-svg-CgMgw0MTJ8u0ruor svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CgMgw0MTJ8u0ruor p{margin:0;}#mermaid-svg-CgMgw0MTJ8u0ruor .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CgMgw0MTJ8u0ruor .cluster-label text{fill:#333;}#mermaid-svg-CgMgw0MTJ8u0ruor .cluster-label span{color:#333;}#mermaid-svg-CgMgw0MTJ8u0ruor .cluster-label span p{background-color:transparent;}#mermaid-svg-CgMgw0MTJ8u0ruor .label text,#mermaid-svg-CgMgw0MTJ8u0ruor span{fill:#333;color:#333;}#mermaid-svg-CgMgw0MTJ8u0ruor .node rect,#mermaid-svg-CgMgw0MTJ8u0ruor .node circle,#mermaid-svg-CgMgw0MTJ8u0ruor .node ellipse,#mermaid-svg-CgMgw0MTJ8u0ruor .node polygon,#mermaid-svg-CgMgw0MTJ8u0ruor .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CgMgw0MTJ8u0ruor .rough-node .label text,#mermaid-svg-CgMgw0MTJ8u0ruor .node .label text,#mermaid-svg-CgMgw0MTJ8u0ruor .image-shape .label,#mermaid-svg-CgMgw0MTJ8u0ruor .icon-shape .label{text-anchor:middle;}#mermaid-svg-CgMgw0MTJ8u0ruor .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CgMgw0MTJ8u0ruor .rough-node .label,#mermaid-svg-CgMgw0MTJ8u0ruor .node .label,#mermaid-svg-CgMgw0MTJ8u0ruor .image-shape .label,#mermaid-svg-CgMgw0MTJ8u0ruor .icon-shape .label{text-align:center;}#mermaid-svg-CgMgw0MTJ8u0ruor .node.clickable{cursor:pointer;}#mermaid-svg-CgMgw0MTJ8u0ruor .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CgMgw0MTJ8u0ruor .arrowheadPath{fill:#333333;}#mermaid-svg-CgMgw0MTJ8u0ruor .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CgMgw0MTJ8u0ruor .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CgMgw0MTJ8u0ruor .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CgMgw0MTJ8u0ruor .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CgMgw0MTJ8u0ruor .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CgMgw0MTJ8u0ruor .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CgMgw0MTJ8u0ruor .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CgMgw0MTJ8u0ruor .cluster text{fill:#333;}#mermaid-svg-CgMgw0MTJ8u0ruor .cluster span{color:#333;}#mermaid-svg-CgMgw0MTJ8u0ruor 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-CgMgw0MTJ8u0ruor .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CgMgw0MTJ8u0ruor rect.text{fill:none;stroke-width:0;}#mermaid-svg-CgMgw0MTJ8u0ruor .icon-shape,#mermaid-svg-CgMgw0MTJ8u0ruor .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CgMgw0MTJ8u0ruor .icon-shape p,#mermaid-svg-CgMgw0MTJ8u0ruor .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CgMgw0MTJ8u0ruor .icon-shape .label rect,#mermaid-svg-CgMgw0MTJ8u0ruor .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CgMgw0MTJ8u0ruor .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CgMgw0MTJ8u0ruor .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CgMgw0MTJ8u0ruor :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 语义化版本 Semantic Versioning
MAJOR.MINOR.PATCH
MAJOR: 不兼容的API修改
MINOR: 向下兼容的功能新增
PATCH: 向下兼容的Bug修复
版本前缀规则
^4.18.2: 兼容更新
~4.18.2: 补丁更新
*: 最新版本
锁定版本: 精确匹配

版本号示例说明:

json 复制代码
{
  "dependencies": {
    "express": "^4.18.2",    // 【代码注释】^ 锁定大版本,>=4.18.2 <5.0.0
    "lodash": "~4.17.21",    // 【代码注释】~ 锁定小版本,>=4.17.21 <4.18.0
    "axios": "1.4.0",        // 【代码注释】精确版本,必须 1.4.0
    "react": "*",            // 【代码注释】任意版本(不推荐)
    "vue": "latest",         // 【代码注释】最新版本(不推荐)
    "moment": "file:./local" // 【代码注释】本地文件
  }
}

版本选择逻辑:

版本范围 匹配版本示例 说明
^1.2.3 1.2.3, 1.3.0, 1.9.9 不更新最左边的非零版本号
~1.2.3 1.2.3, 1.2.9 只更新补丁版本
* 2.0.0, 3.0.0 任意版本
1.x 1.0.0, 1.9.9 任意 1.x.x 版本
>=1.2.3 1.2.3, 2.0.0 大于等于指定版本
1.2.3 - 2.0.0 1.2.3, 1.9.9, 2.0.0 版本范围
package-lock.json 文件

名词解释:package-lock.json

package-lock.json 是 npm 自动生成的文件,用于锁定项目依赖的精确版本号,确保团队成员安装相同版本的依赖包。

作用:

  1. 锁定依赖包的确切版本
  2. 提高安装速度(跳过版本解析)
  3. 确保团队成员使用相同的依赖版本
  4. 记录依赖包的完整依赖树

示例结构:

json 复制代码
{
  "name": "project",
  "version": "1.0.0",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {
    "node_modules/express": {
      "version": "4.18.2",
      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
      "integrity": "sha512-...",
      "dependencies": {
        "accepts": "~1.3.8",
        "array-flatten": "1.1.1"
      }
    }
  }
}

【代码注释】

  • package-lock.json 记录精确版本 与下载地址(resolvedintegrity),提交到 Git 后队友 npm ci 装出相同树。
  • lockfileVersion 随 npm 大版本变化;勿手改 lock,用 npm install 让工具更新。
  • package.json^ 范围的关系:install 在范围内解析一次并锁定 ;后续 ci 不再重新解析范围。
  • 删 lock 再 install 可能升级到依赖的最新次版本,导致「我这能跑你那不行」------团队应统一保留 lock。

2.4 模块查找机制

Node.js 的模块查找机制遵循特定的优先级顺序,理解这个机制对于解决模块加载问题非常重要。

查找流程图

#mermaid-svg-UVZDvo6nGuEFsmUu{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-UVZDvo6nGuEFsmUu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UVZDvo6nGuEFsmUu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UVZDvo6nGuEFsmUu .error-icon{fill:#552222;}#mermaid-svg-UVZDvo6nGuEFsmUu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UVZDvo6nGuEFsmUu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UVZDvo6nGuEFsmUu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UVZDvo6nGuEFsmUu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UVZDvo6nGuEFsmUu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UVZDvo6nGuEFsmUu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UVZDvo6nGuEFsmUu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UVZDvo6nGuEFsmUu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UVZDvo6nGuEFsmUu .marker.cross{stroke:#333333;}#mermaid-svg-UVZDvo6nGuEFsmUu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UVZDvo6nGuEFsmUu p{margin:0;}#mermaid-svg-UVZDvo6nGuEFsmUu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UVZDvo6nGuEFsmUu .cluster-label text{fill:#333;}#mermaid-svg-UVZDvo6nGuEFsmUu .cluster-label span{color:#333;}#mermaid-svg-UVZDvo6nGuEFsmUu .cluster-label span p{background-color:transparent;}#mermaid-svg-UVZDvo6nGuEFsmUu .label text,#mermaid-svg-UVZDvo6nGuEFsmUu span{fill:#333;color:#333;}#mermaid-svg-UVZDvo6nGuEFsmUu .node rect,#mermaid-svg-UVZDvo6nGuEFsmUu .node circle,#mermaid-svg-UVZDvo6nGuEFsmUu .node ellipse,#mermaid-svg-UVZDvo6nGuEFsmUu .node polygon,#mermaid-svg-UVZDvo6nGuEFsmUu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UVZDvo6nGuEFsmUu .rough-node .label text,#mermaid-svg-UVZDvo6nGuEFsmUu .node .label text,#mermaid-svg-UVZDvo6nGuEFsmUu .image-shape .label,#mermaid-svg-UVZDvo6nGuEFsmUu .icon-shape .label{text-anchor:middle;}#mermaid-svg-UVZDvo6nGuEFsmUu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UVZDvo6nGuEFsmUu .rough-node .label,#mermaid-svg-UVZDvo6nGuEFsmUu .node .label,#mermaid-svg-UVZDvo6nGuEFsmUu .image-shape .label,#mermaid-svg-UVZDvo6nGuEFsmUu .icon-shape .label{text-align:center;}#mermaid-svg-UVZDvo6nGuEFsmUu .node.clickable{cursor:pointer;}#mermaid-svg-UVZDvo6nGuEFsmUu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UVZDvo6nGuEFsmUu .arrowheadPath{fill:#333333;}#mermaid-svg-UVZDvo6nGuEFsmUu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UVZDvo6nGuEFsmUu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UVZDvo6nGuEFsmUu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UVZDvo6nGuEFsmUu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UVZDvo6nGuEFsmUu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UVZDvo6nGuEFsmUu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UVZDvo6nGuEFsmUu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UVZDvo6nGuEFsmUu .cluster text{fill:#333;}#mermaid-svg-UVZDvo6nGuEFsmUu .cluster span{color:#333;}#mermaid-svg-UVZDvo6nGuEFsmUu 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-UVZDvo6nGuEFsmUu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UVZDvo6nGuEFsmUu rect.text{fill:none;stroke-width:0;}#mermaid-svg-UVZDvo6nGuEFsmUu .icon-shape,#mermaid-svg-UVZDvo6nGuEFsmUu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UVZDvo6nGuEFsmUu .icon-shape p,#mermaid-svg-UVZDvo6nGuEFsmUu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UVZDvo6nGuEFsmUu .icon-shape .label rect,#mermaid-svg-UVZDvo6nGuEFsmUu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UVZDvo6nGuEFsmUu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UVZDvo6nGuEFsmUu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UVZDvo6nGuEFsmUu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ./ 或 ../ 开头
/ 或绝对路径
其他
是核心模块
不是
找到
未找到
找到
未找到
require&'module-name&'
路径类型判断
相对路径模块
绝对路径模块
核心模块或第三方模块
加载文件模块
核心模块检查
加载核心模块
第三方模块
node_modules 查找
当前目录 node_modules?
加载模块
上级目录查找
上级 node_modules?
继续向上查找
到达根目录
抛出 MODULE_NOT_FOUND

查找顺序详解

1. 相对路径模块(./../ 开头)

javascript 复制代码
// 【代码注释】查找当前目录下的 utils.js
const utils = require('./utils');

// 【代码注释】查找上级目录的 helper.js
const helper = require('../helper');

// 【代码注释】查找子目录下的 config.json
const config = require('./config/config.json');

2. 绝对路径模块

javascript 复制代码
// 【代码注释】使用绝对路径加载模块
const module = require('/absolute/path/to/module.js');

3. 核心模块

javascript 复制代码
// 【代码注释】Node.js 内置模块,优先级最高
const fs = require('fs');        // 文件系统模块
const http = require('http');    // HTTP 服务器模块
const path = require('path');    // 路径处理模块
// 【代码注释】核心模块不需要路径,直接加载

4. 第三方模块查找

javascript 复制代码
// 【代码注释】从 node_modules 目录查找
const express = require('express');

// 【代码注释】查找顺序示例:
// 假设在 /home/user/project/app.js 中 require('express')
// 1. /home/user/project/node_modules/express
// 2. /home/user/node_modules/express
// 3. /home/node_modules/express
// 4. /node_modules/express

5. 目录模块处理

javascript 复制代码
// 【代码注释】require('./utils') 会查找:
// 1. utils.js
// 2. utils.json
// 3. utils.node
// 4. utils/package.json (查找 main 字段)
// 5. utils/index.js

实践示例:

javascript 复制代码
// 【代码注释】项目结构
// project/
// ├── node_modules/
// │   └── lodash/
// ├── src/
// │   ├── node_modules/
// │   │   └── axios/
// │   └── app.js
// └── package.json

// 【代码注释】在 src/app.js 中:

// 【代码注释】1. 核心模块
const fs = require('fs');  // 直接加载核心模块

// 【代码注释】2. 项目级第三方模块
const _ = require('lodash');  // 查找 project/node_modules/lodash

// 【代码注释】3. 目录级第三方模块
const axios = require('axios');  // 查找 src/node_modules/axios

// 【代码注释】4. 相对路径模块
const utils = require('./utils');  // 查找 src/utils.js

【代码注释】

  • require('lodash')当前文件所在目录 向上逐层找 node_modules,故 src/node_modules/axios 可覆盖/补充项目根依赖(npm 依赖提升后常见扁平结构在根 node_modules)。
  • require('fs') 不走 node_modules,由 Node 内置加载器直接提供。
  • require('./utils') 绝不查找 node_modules,只解析相对路径文件/目录。
  • 报错 Cannot find module 'xxx':先区分是第三方包(未 install)还是相对路径写错。

2.5 远程仓库协作工作流程

在团队协作开发中,正确处理 node_modules 和依赖管理是确保开发环境一致性的关键。
NPM 仓库 Git 仓库 开发者 NPM 仓库 Git 仓库 开发者 #mermaid-svg-IMmznh1iqXurtgOO{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-IMmznh1iqXurtgOO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IMmznh1iqXurtgOO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IMmznh1iqXurtgOO .error-icon{fill:#552222;}#mermaid-svg-IMmznh1iqXurtgOO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IMmznh1iqXurtgOO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IMmznh1iqXurtgOO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IMmznh1iqXurtgOO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IMmznh1iqXurtgOO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IMmznh1iqXurtgOO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IMmznh1iqXurtgOO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IMmznh1iqXurtgOO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IMmznh1iqXurtgOO .marker.cross{stroke:#333333;}#mermaid-svg-IMmznh1iqXurtgOO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IMmznh1iqXurtgOO p{margin:0;}#mermaid-svg-IMmznh1iqXurtgOO .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IMmznh1iqXurtgOO text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-IMmznh1iqXurtgOO .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IMmznh1iqXurtgOO .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-IMmznh1iqXurtgOO .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-IMmznh1iqXurtgOO .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-IMmznh1iqXurtgOO #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-IMmznh1iqXurtgOO .sequenceNumber{fill:white;}#mermaid-svg-IMmznh1iqXurtgOO #sequencenumber{fill:#333;}#mermaid-svg-IMmznh1iqXurtgOO #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-IMmznh1iqXurtgOO .messageText{fill:#333;stroke:none;}#mermaid-svg-IMmznh1iqXurtgOO .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IMmznh1iqXurtgOO .labelText,#mermaid-svg-IMmznh1iqXurtgOO .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-IMmznh1iqXurtgOO .loopText,#mermaid-svg-IMmznh1iqXurtgOO .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-IMmznh1iqXurtgOO .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-IMmznh1iqXurtgOO .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-IMmznh1iqXurtgOO .noteText,#mermaid-svg-IMmznh1iqXurtgOO .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-IMmznh1iqXurtgOO .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IMmznh1iqXurtgOO .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IMmznh1iqXurtgOO .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IMmznh1iqXurtgOO .actorPopupMenu{position:absolute;}#mermaid-svg-IMmznh1iqXurtgOO .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-IMmznh1iqXurtgOO .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IMmznh1iqXurtgOO .actor-man circle,#mermaid-svg-IMmznh1iqXurtgOO line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-IMmznh1iqXurtgOO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 团队协作开发流程 .gitignore 忽略 node_modules 1. 克隆项目到本地 项目代码(无 node_modules) 2. npm install 安装依赖 依赖包安装到 node_modules/ 3. 进行开发工作 4. 提交并推送代码 5. 下次拉取最新代码 更新的代码 6. npm install(如有新依赖) 新依赖包安装

实际工作流程

新员工入职第一天:

bash 复制代码
# 【命令注释】1. 克隆项目代码
git clone https://github.com/company/project.git
cd project

# 【命令注释】2. 安装项目依赖
npm install
# 【代码注释】根据 package.json 安装所有依赖
# 【代码注释】生成 node_modules 目录

# 【命令注释】3. 启动开发环境
npm run dev
# 或
npm start

日常开发流程:

bash 复制代码
# 【命令注释】1. 拉取最新代码
git pull origin main

# 【命令注释】2. 检查是否有新的依赖
git diff package.json
git diff package-lock.json

# 【命令注释】3. 安装新依赖(如有)
npm install

# 【命令注释】4. 继续开发
npm run dev

# 【命令注释】5. 提交代码前安装新依赖(如添加了新包)
git add package.json package-lock.json
git commit -m "Add new feature"
git push origin feature-branch

.gitignore 配置:

gitignore 复制代码
# 【代码注释】.gitignore 文件内容
# 依赖目录(不要提交 node_modules)
node_modules/

# 【代码注释】package-lock.json 应提交到 Git,保证团队依赖版本一致

# 日志文件
*.log
npm-debug.log*

# 环境变量文件
.env
.env.local

# 操作系统文件
.DS_Store
Thumbs.db

# IDE 配置文件
.vscode/
.idea/
*.swp
*.swo

2.6 配置命令别名

通过 package.json 的 scripts 字段配置命令别名,可以简化常用命令的执行。

基本配置
json 复制代码
{
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js",
    "test": "jest",
    "build": "webpack --mode production",
    "lint": "eslint src/**/*.js",
    "format": "prettier --write \"src/**/*.js\""
  }
}

使用示例:

bash 复制代码
# 【命令注释】使用配置的别名
npm start          # 等同于 node src/index.js
npm run dev        # 等同于 nodemon src/index.js
npm test           # 等同于 jest(可省略 run)
npm run build      # 等同于 webpack --mode production
npm run lint       # 等同于 eslint src/**/*.js

【代码注释】

  • scripts 里的命令在项目根目录 的 shell 环境中执行;路径、环境变量与你在终端 cd 的位置有关(npm 会先定位到含 package.json 的目录)。
  • npm startnpm testnpm stop 等是 npm 内置生命周期 ,可省略 run;自定义脚本如 devbuild 必须 npm run dev
  • prebuild / postbuild 等钩子:执行 npm run build 时会自动先跑 prebuild、后跑 postbuild(若已定义)。
  • Windows 下设置环境变量建议用 cross-env,避免 NODE_ENV=production 语法不兼容。

npm run 的向上查找特性:

require() 类似,若在子目录执行 npm run xxx 且当前目录没有 package.json,npm 会向上级目录 查找最近的 package.json 再执行对应脚本。适合 monorepo 或在 src/ 子目录临时启动根项目脚本的场景。

bash 复制代码
# 【命令注释】在 monorepo 子包目录,仍可能触发根目录 scripts(取决于 npm 版本与 cwd)
cd packages/ui
npm run build   # 若本目录无 package.json,可能使用上级仓库的配置

【代码注释】

  • 克隆仓库后第一步:npm install(或 pnpm i / yarn),再打开 package.jsonscripts 查看 dev/start/build 含义。
  • monorepo 子目录执行 npm run build 时,npm 可能向上找到 package.json 的脚本(与版本、cwd 有关),避免在错误目录误启服务。
  • 常见约定:start 生产启动、dev 热更新开发、test 单测、lint 静态检查;无 start 时看 README 说明。
高级配置技巧

1. 传递参数:

json 复制代码
{
  "scripts": {
    "start": "node src/index.js",
    "start:prod": "NODE_ENV=production npm start",
    "start:dev": "NODE_ENV=development npm start",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

2. 组合命令:

json 复制代码
{
  "scripts": {
    "clean": "rm -rf dist",
    "prebuild": "npm run clean",
    "build": "webpack --mode production",
    "postbuild": "npm run test",
    "rebuild": "npm run clean && npm run build"
  }
}

3. 环境变量:

json 复制代码
{
  "scripts": {
    "start": "cross-env NODE_ENV=production node src/index.js",
    "dev": "cross-env NODE_ENV=development nodemon src/index.js",
    "test": "cross-env NODE_ENV=test jest"
  }
}

4. 平台兼容:

json 复制代码
{
  "scripts": {
    "clean": "rimraf dist",
    "preinstall": "npx only-allow pnpm"
  }
}
生命周期钩子

npm scripts 支持生命周期钩子,某些操作会自动触发相关钩子:
#mermaid-svg-gP07KOdbOTW00TRq{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-gP07KOdbOTW00TRq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gP07KOdbOTW00TRq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gP07KOdbOTW00TRq .error-icon{fill:#552222;}#mermaid-svg-gP07KOdbOTW00TRq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gP07KOdbOTW00TRq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gP07KOdbOTW00TRq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gP07KOdbOTW00TRq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gP07KOdbOTW00TRq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gP07KOdbOTW00TRq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gP07KOdbOTW00TRq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gP07KOdbOTW00TRq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gP07KOdbOTW00TRq .marker.cross{stroke:#333333;}#mermaid-svg-gP07KOdbOTW00TRq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gP07KOdbOTW00TRq p{margin:0;}#mermaid-svg-gP07KOdbOTW00TRq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gP07KOdbOTW00TRq .cluster-label text{fill:#333;}#mermaid-svg-gP07KOdbOTW00TRq .cluster-label span{color:#333;}#mermaid-svg-gP07KOdbOTW00TRq .cluster-label span p{background-color:transparent;}#mermaid-svg-gP07KOdbOTW00TRq .label text,#mermaid-svg-gP07KOdbOTW00TRq span{fill:#333;color:#333;}#mermaid-svg-gP07KOdbOTW00TRq .node rect,#mermaid-svg-gP07KOdbOTW00TRq .node circle,#mermaid-svg-gP07KOdbOTW00TRq .node ellipse,#mermaid-svg-gP07KOdbOTW00TRq .node polygon,#mermaid-svg-gP07KOdbOTW00TRq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gP07KOdbOTW00TRq .rough-node .label text,#mermaid-svg-gP07KOdbOTW00TRq .node .label text,#mermaid-svg-gP07KOdbOTW00TRq .image-shape .label,#mermaid-svg-gP07KOdbOTW00TRq .icon-shape .label{text-anchor:middle;}#mermaid-svg-gP07KOdbOTW00TRq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gP07KOdbOTW00TRq .rough-node .label,#mermaid-svg-gP07KOdbOTW00TRq .node .label,#mermaid-svg-gP07KOdbOTW00TRq .image-shape .label,#mermaid-svg-gP07KOdbOTW00TRq .icon-shape .label{text-align:center;}#mermaid-svg-gP07KOdbOTW00TRq .node.clickable{cursor:pointer;}#mermaid-svg-gP07KOdbOTW00TRq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gP07KOdbOTW00TRq .arrowheadPath{fill:#333333;}#mermaid-svg-gP07KOdbOTW00TRq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gP07KOdbOTW00TRq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gP07KOdbOTW00TRq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gP07KOdbOTW00TRq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gP07KOdbOTW00TRq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gP07KOdbOTW00TRq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gP07KOdbOTW00TRq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gP07KOdbOTW00TRq .cluster text{fill:#333;}#mermaid-svg-gP07KOdbOTW00TRq .cluster span{color:#333;}#mermaid-svg-gP07KOdbOTW00TRq 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-gP07KOdbOTW00TRq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gP07KOdbOTW00TRq rect.text{fill:none;stroke-width:0;}#mermaid-svg-gP07KOdbOTW00TRq .icon-shape,#mermaid-svg-gP07KOdbOTW00TRq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gP07KOdbOTW00TRq .icon-shape p,#mermaid-svg-gP07KOdbOTW00TRq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gP07KOdbOTW00TRq .icon-shape .label rect,#mermaid-svg-gP07KOdbOTW00TRq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gP07KOdbOTW00TRq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gP07KOdbOTW00TRq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gP07KOdbOTW00TRq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} npm install
preinstall
install
postinstall
npm publish
prepublishOnly
prepare
prepublish
publish
postpublish
npm start
prestart
start
poststart

实际应用:

json 复制代码
{
  "scripts": {
    "preinstall": "node scripts/check-node-version.js",
    "postinstall": "node scripts/post-install.js",
    "prestart": "npm run build",
    "start": "node dist/index.js",
    "prepublishOnly": "npm run build && npm test",
    "prepare": "husky install"
  }
}

3. 国内镜像工具

由于网络原因,在中国大陆地区使用官方 npm 仓库可能较慢,因此需要使用国内镜像工具。

3.1 cnpm 淘宝镜像

名词解释:cnpm

cnpm 是淘宝团队提供的 npm 镜像服务,将 npm 官方仓库的包同步到国内服务器,大幅提升下载速度。

安装与配置方式

方式一:安装 cnpm 命令行工具

bash 复制代码
# 【命令注释】全局安装 cnpm
npm install -g cnpm --registry=https://registry.npmmirror.com

# 【命令注释】使用 cnpm 代替 npm
cnpm install express
cnpm install -g nodemon
cnpm update

方式二:Shell 别名(不改全局 registry,临时走镜像)

bash 复制代码
# 【命令注释】在 ~/.zshrc 或 ~/.bashrc 中定义(路径按本机 HOME 调整)
alias cnpm="npm --registry=https://registry.npmmirror.com \
  --cache=$HOME/.npm/.cache/cnpm \
  --disturl=https://npmmirror.com/dist \
  --userconfig=$HOME/.cnpmrc"

cnpm install express

【代码注释】

  • alias cnpm="npm --registry=..." 不改全局 registry,仅在该命令走 npmmirror.com;适合公司内网与官方源混用。
  • --cache / --userconfig 分离 cnpm 缓存与配置,避免与官方 npm 缓存冲突。
  • npm publish 必须对官方源 :发布前执行 npm config get registry,应为 https://registry.npmjs.org/
  • 全局安装 cnpm 后命令与 npm 并列;团队更常用 nrm.npmrc 项目级 registry 统一源。

方式三:配置 npm 使用淘宝镜像

bash 复制代码
# 【命令注释】查看当前镜像源
npm config get registry
# 默认: https://registry.npmjs.org/

# 【命令注释】设置为淘宝镜像
npm config set registry https://registry.npmmirror.com

# 【命令注释】验证配置
npm config get registry
# 输出: https://registry.npmmirror.com

# 【命令注释】恢复官方镜像
npm config set registry https://registry.npmjs.org/

方式四:使用 nrm 管理镜像源

bash 复制代码
# 【命令注释】安装 nrm(镜像源管理工具)
npm install -g nrm

# 【命令注释】查看可用镜像源
nrm ls
# * npm -------- https://registry.npmjs.org/
#   yarn ------- https://registry.yarnpkg.com/
#   tencent ---- https://mirrors.cloud.tencent.com/npm/
#   cnpm ------- https://registry.npmmirror.com/
#   taobao ----- https://registry.npmmirror.com/
#   npmMirror -- https://skimdb.npmjs.com/registry/

# 【命令注释】切换镜像源
nrm use taobao
# 【命令注释】输出: The registry has been changed to 'taobao'

# 【命令注释】测试镜像源速度
nrm test

镜像源对比:

镜像源 URL 特点
npm 官方 https://registry.npmjs.org/ 最全最新,但速度较慢
淘宝镜像 https://registry.npmmirror.com/ 速度快,更新频繁
腾讯云 https://mirrors.cloud.tencent.com/npm/ 速度快,稳定性好
华为云 https://repo.huaweicloud.com/repository/npm/ 速度快,企业级

3.2 yarn 包管理工具

名词解释:Yarn

Yarn 是 Facebook 开发的 JavaScript 包管理工具,旨在解决 npm 在性能和一致性方面的问题。它提供了更快的安装速度和更可靠的依赖管理。

Yarn vs npm 对比

#mermaid-svg-yxXvN5gmG82rMtQL{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-yxXvN5gmG82rMtQL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yxXvN5gmG82rMtQL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yxXvN5gmG82rMtQL .error-icon{fill:#552222;}#mermaid-svg-yxXvN5gmG82rMtQL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yxXvN5gmG82rMtQL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yxXvN5gmG82rMtQL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yxXvN5gmG82rMtQL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yxXvN5gmG82rMtQL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yxXvN5gmG82rMtQL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yxXvN5gmG82rMtQL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yxXvN5gmG82rMtQL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yxXvN5gmG82rMtQL .marker.cross{stroke:#333333;}#mermaid-svg-yxXvN5gmG82rMtQL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yxXvN5gmG82rMtQL p{margin:0;}#mermaid-svg-yxXvN5gmG82rMtQL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yxXvN5gmG82rMtQL .cluster-label text{fill:#333;}#mermaid-svg-yxXvN5gmG82rMtQL .cluster-label span{color:#333;}#mermaid-svg-yxXvN5gmG82rMtQL .cluster-label span p{background-color:transparent;}#mermaid-svg-yxXvN5gmG82rMtQL .label text,#mermaid-svg-yxXvN5gmG82rMtQL span{fill:#333;color:#333;}#mermaid-svg-yxXvN5gmG82rMtQL .node rect,#mermaid-svg-yxXvN5gmG82rMtQL .node circle,#mermaid-svg-yxXvN5gmG82rMtQL .node ellipse,#mermaid-svg-yxXvN5gmG82rMtQL .node polygon,#mermaid-svg-yxXvN5gmG82rMtQL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yxXvN5gmG82rMtQL .rough-node .label text,#mermaid-svg-yxXvN5gmG82rMtQL .node .label text,#mermaid-svg-yxXvN5gmG82rMtQL .image-shape .label,#mermaid-svg-yxXvN5gmG82rMtQL .icon-shape .label{text-anchor:middle;}#mermaid-svg-yxXvN5gmG82rMtQL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yxXvN5gmG82rMtQL .rough-node .label,#mermaid-svg-yxXvN5gmG82rMtQL .node .label,#mermaid-svg-yxXvN5gmG82rMtQL .image-shape .label,#mermaid-svg-yxXvN5gmG82rMtQL .icon-shape .label{text-align:center;}#mermaid-svg-yxXvN5gmG82rMtQL .node.clickable{cursor:pointer;}#mermaid-svg-yxXvN5gmG82rMtQL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yxXvN5gmG82rMtQL .arrowheadPath{fill:#333333;}#mermaid-svg-yxXvN5gmG82rMtQL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yxXvN5gmG82rMtQL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yxXvN5gmG82rMtQL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yxXvN5gmG82rMtQL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yxXvN5gmG82rMtQL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yxXvN5gmG82rMtQL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yxXvN5gmG82rMtQL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yxXvN5gmG82rMtQL .cluster text{fill:#333;}#mermaid-svg-yxXvN5gmG82rMtQL .cluster span{color:#333;}#mermaid-svg-yxXvN5gmG82rMtQL 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-yxXvN5gmG82rMtQL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yxXvN5gmG82rMtQL rect.text{fill:none;stroke-width:0;}#mermaid-svg-yxXvN5gmG82rMtQL .icon-shape,#mermaid-svg-yxXvN5gmG82rMtQL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yxXvN5gmG82rMtQL .icon-shape p,#mermaid-svg-yxXvN5gmG82rMtQL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yxXvN5gmG82rMtQL .icon-shape .label rect,#mermaid-svg-yxXvN5gmG82rMtQL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yxXvN5gmG82rMtQL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yxXvN5gmG82rMtQL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yxXvN5gmG82rMtQL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} npm 特点
串行安装
缓存机制
依赖管理
官方支持
Yarn 特点
并行安装
本地缓存
版本锁定
网络优化

性能对比:

  • 安装速度:Yarn 并行安装包,通常比 npm 快 2-3 倍
  • 缓存机制:Yarn 缓存已下载的包,避免重复下载
  • 版本一致性:Yarn 使用 yarn.lock 确保版本一致性
Yarn 常用命令
bash 复制代码
# 【命令注释】安装 yarn
npm install -g yarn
# 或使用 npm 安装(不推荐全局)
corepack enable  # Node.js 16.10+ 内置 yarn 支持

# 【命令注释】初始化项目
yarn init
yarn init -y  # 快速初始化

# 【命令注释】安装依赖
yarn add express          # 添加到 dependencies
yarn add jest --dev       # 添加到 devDependencies
yarn global add nodemon   # 全局安装

# 【命令注释】安装项目依赖
yarn install
# 或
yarn

# 【命令注释】更新依赖
yarn upgrade
yarn upgrade express     # 更新特定包

# 【命令注释】删除依赖
yarn remove express
yarn global remove nodemon

# 【命令注释】运行脚本
yarn start
yarn build
yarn test

# 【命令注释】查看信息
yarn list
yarn info express

npm vs yarn 命令对照表:

操作 npm Yarn
安装依赖 npm install yarn / yarn install
添加包 npm install package yarn add package
开发依赖 npm install -D package yarn add package --dev
全局安装 npm install -g package yarn global add package
更新包 npm update package yarn upgrade package
删除包 npm uninstall package yarn remove package
运行脚本 npm run command yarn command
全局运行 npx command yarn dlx package
cyarn(淘宝镜像版 yarn)
bash 复制代码
# 【命令注释】安装 cyarn(使用淘宝镜像的 yarn)
npm install -g cyarn --registry "https://registry.npmmirror.com"

# 【命令注释】使用方式与 yarn 相同
cyarn install
cyarn add express
cyarn build

3.3 npx 命令工具

名词解释:npx

npx 是 npm 5.2.0+ 自带的命令执行工具,可以直接执行 npm 包中的命令行工具,无需全局安装。

npx 的优势

#mermaid-svg-ho95IMo4yfFlsns6{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-ho95IMo4yfFlsns6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ho95IMo4yfFlsns6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ho95IMo4yfFlsns6 .error-icon{fill:#552222;}#mermaid-svg-ho95IMo4yfFlsns6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ho95IMo4yfFlsns6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ho95IMo4yfFlsns6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ho95IMo4yfFlsns6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ho95IMo4yfFlsns6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ho95IMo4yfFlsns6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ho95IMo4yfFlsns6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ho95IMo4yfFlsns6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ho95IMo4yfFlsns6 .marker.cross{stroke:#333333;}#mermaid-svg-ho95IMo4yfFlsns6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ho95IMo4yfFlsns6 p{margin:0;}#mermaid-svg-ho95IMo4yfFlsns6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ho95IMo4yfFlsns6 .cluster-label text{fill:#333;}#mermaid-svg-ho95IMo4yfFlsns6 .cluster-label span{color:#333;}#mermaid-svg-ho95IMo4yfFlsns6 .cluster-label span p{background-color:transparent;}#mermaid-svg-ho95IMo4yfFlsns6 .label text,#mermaid-svg-ho95IMo4yfFlsns6 span{fill:#333;color:#333;}#mermaid-svg-ho95IMo4yfFlsns6 .node rect,#mermaid-svg-ho95IMo4yfFlsns6 .node circle,#mermaid-svg-ho95IMo4yfFlsns6 .node ellipse,#mermaid-svg-ho95IMo4yfFlsns6 .node polygon,#mermaid-svg-ho95IMo4yfFlsns6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ho95IMo4yfFlsns6 .rough-node .label text,#mermaid-svg-ho95IMo4yfFlsns6 .node .label text,#mermaid-svg-ho95IMo4yfFlsns6 .image-shape .label,#mermaid-svg-ho95IMo4yfFlsns6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-ho95IMo4yfFlsns6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ho95IMo4yfFlsns6 .rough-node .label,#mermaid-svg-ho95IMo4yfFlsns6 .node .label,#mermaid-svg-ho95IMo4yfFlsns6 .image-shape .label,#mermaid-svg-ho95IMo4yfFlsns6 .icon-shape .label{text-align:center;}#mermaid-svg-ho95IMo4yfFlsns6 .node.clickable{cursor:pointer;}#mermaid-svg-ho95IMo4yfFlsns6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ho95IMo4yfFlsns6 .arrowheadPath{fill:#333333;}#mermaid-svg-ho95IMo4yfFlsns6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ho95IMo4yfFlsns6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ho95IMo4yfFlsns6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ho95IMo4yfFlsns6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ho95IMo4yfFlsns6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ho95IMo4yfFlsns6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ho95IMo4yfFlsns6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ho95IMo4yfFlsns6 .cluster text{fill:#333;}#mermaid-svg-ho95IMo4yfFlsns6 .cluster span{color:#333;}#mermaid-svg-ho95IMo4yfFlsns6 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-ho95IMo4yfFlsns6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ho95IMo4yfFlsns6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-ho95IMo4yfFlsns6 .icon-shape,#mermaid-svg-ho95IMo4yfFlsns6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ho95IMo4yfFlsns6 .icon-shape p,#mermaid-svg-ho95IMo4yfFlsns6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ho95IMo4yfFlsns6 .icon-shape .label rect,#mermaid-svg-ho95IMo4yfFlsns6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ho95IMo4yfFlsns6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ho95IMo4yfFlsns6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ho95IMo4yfFlsns6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} npx 优势
无需全局安装
总是使用最新版本
自动下载执行
节省磁盘空间
避免版本冲突
确保工具最新
一键执行命令
按需使用工具

npx 使用示例

1. 执行一次性命令:

bash 复制代码
# 【命令注释】创建 React 应用(无需安装 create-react-app)
npx create-react-app my-app

# 【命令注释】运行 TypeScript 编译器(无需全局安装)
npx typescript --version

# 【命令注释】使用 prettier 格式化代码
npx prettier --write src/**/*.js

2. 指定版本执行:

bash 复制代码
# 【命令注释】使用特定版本的包
npx create-react-app@5.0.0 my-app
npx typescript@4.9.5 --version

# 【命令注释】使用不同版本的 Node.js
npx -p node@16 npm start
npx -p node@18 npm test

3. 从 Git 仓库执行:

bash 复制代码
# 【命令注释】直接从 GitHub 执行包
npx github:user/repo
npx bitnamicharts/repo-name

4. 执行本地包:

bash 复制代码
# 【命令注释】执行 node_modules/.bin 中的命令
npx jest
npx eslint src/**/*.js
npx webpack --config webpack.config.js

5. 创建别名:

bash 复制代码
# 【命令注释】创建常用命令的别名
npx --yes npm-check-updates
npx -y create-next-app@latest

# 【代码注释】-y 或 --yes 跳过确认提示

实际应用场景:

bash 复制代码
# 【命令注释】1. 项目脚手架
npx create-vue@latest my-project
npx create-react-app my-app
npx @angular/cli new my-project

# 【命令注释】2. 代码质量工具
npx prettier --check "src/**/*.js"
npx eslint --fix src/
npx stylelint "css/**/*.css"

# 【命令注释】3. 构建工具
npx webpack --mode production
npx rollup -c
npx parcel build src/index.html

# 【命令注释】4. 测试工具
npx vitest
npx cypress open
npx jest --coverage

# 【命令注释】5. 文档生成
npx typedoc src/
npx jsdoc -c jsdoc.conf.json

【代码注释】

  • npx create-react-app临时下载 包到缓存并执行其 bin,不污染全局 node_modules,用完可删缓存。
  • 执行本地项目脚本:npx jest 等价于 ./node_modules/.bin/jest,保证用的是本项目锁定版本的 jest。
  • -p node@18 可指定用某版本 Node 跑后续命令,多版本共存时有用。
  • -y / --yes 跳过「是否安装」确认,适合 CI 非交互环境。

3.4 pnpm 高性能包管理工具

名词解释:pnpm

pnpm(Performant npm)是一个高效的 JavaScript 包管理工具,通过**内容寻址存储(Content-Addressable Storage)**技术在全局存储中只保存每个版本的依赖一份,再用符号链接(symlink)引用到各项目的 node_modules,从而大幅节省磁盘空间并加快安装速度。Vite、Vue、Turborepo 等主流项目均采用 pnpm。

为什么选择 pnpm?

#mermaid-svg-wfyPl67yhjegBlM6{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-wfyPl67yhjegBlM6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wfyPl67yhjegBlM6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wfyPl67yhjegBlM6 .error-icon{fill:#552222;}#mermaid-svg-wfyPl67yhjegBlM6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wfyPl67yhjegBlM6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wfyPl67yhjegBlM6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wfyPl67yhjegBlM6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wfyPl67yhjegBlM6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wfyPl67yhjegBlM6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wfyPl67yhjegBlM6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wfyPl67yhjegBlM6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wfyPl67yhjegBlM6 .marker.cross{stroke:#333333;}#mermaid-svg-wfyPl67yhjegBlM6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wfyPl67yhjegBlM6 p{margin:0;}#mermaid-svg-wfyPl67yhjegBlM6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wfyPl67yhjegBlM6 .cluster-label text{fill:#333;}#mermaid-svg-wfyPl67yhjegBlM6 .cluster-label span{color:#333;}#mermaid-svg-wfyPl67yhjegBlM6 .cluster-label span p{background-color:transparent;}#mermaid-svg-wfyPl67yhjegBlM6 .label text,#mermaid-svg-wfyPl67yhjegBlM6 span{fill:#333;color:#333;}#mermaid-svg-wfyPl67yhjegBlM6 .node rect,#mermaid-svg-wfyPl67yhjegBlM6 .node circle,#mermaid-svg-wfyPl67yhjegBlM6 .node ellipse,#mermaid-svg-wfyPl67yhjegBlM6 .node polygon,#mermaid-svg-wfyPl67yhjegBlM6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wfyPl67yhjegBlM6 .rough-node .label text,#mermaid-svg-wfyPl67yhjegBlM6 .node .label text,#mermaid-svg-wfyPl67yhjegBlM6 .image-shape .label,#mermaid-svg-wfyPl67yhjegBlM6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-wfyPl67yhjegBlM6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wfyPl67yhjegBlM6 .rough-node .label,#mermaid-svg-wfyPl67yhjegBlM6 .node .label,#mermaid-svg-wfyPl67yhjegBlM6 .image-shape .label,#mermaid-svg-wfyPl67yhjegBlM6 .icon-shape .label{text-align:center;}#mermaid-svg-wfyPl67yhjegBlM6 .node.clickable{cursor:pointer;}#mermaid-svg-wfyPl67yhjegBlM6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wfyPl67yhjegBlM6 .arrowheadPath{fill:#333333;}#mermaid-svg-wfyPl67yhjegBlM6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wfyPl67yhjegBlM6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wfyPl67yhjegBlM6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wfyPl67yhjegBlM6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wfyPl67yhjegBlM6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wfyPl67yhjegBlM6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wfyPl67yhjegBlM6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wfyPl67yhjegBlM6 .cluster text{fill:#333;}#mermaid-svg-wfyPl67yhjegBlM6 .cluster span{color:#333;}#mermaid-svg-wfyPl67yhjegBlM6 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-wfyPl67yhjegBlM6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wfyPl67yhjegBlM6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-wfyPl67yhjegBlM6 .icon-shape,#mermaid-svg-wfyPl67yhjegBlM6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wfyPl67yhjegBlM6 .icon-shape p,#mermaid-svg-wfyPl67yhjegBlM6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wfyPl67yhjegBlM6 .icon-shape .label rect,#mermaid-svg-wfyPl67yhjegBlM6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wfyPl67yhjegBlM6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wfyPl67yhjegBlM6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wfyPl67yhjegBlM6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pnpm 核心优势
节省磁盘空间
安装速度快
严格的依赖隔离
原生 Monorepo 支持
全局内容寻址存储
相同包版本只存一份
已存在的包跳过下载
并行安装依赖
防止幽灵依赖访问
符号链接严格隔离
workspace 协议
--filter 精确执行

幽灵依赖(Phantom Dependency) :npm/yarn 将所有依赖扁平化到根 node_modules,导致项目能访问未在 package.json 中声明的包。一旦上游包升级或删除,代码就会崩溃。pnpm 的符号链接结构从根本上杜绝了这个问题。

pnpm vs npm vs yarn 对比:

特性 npm yarn pnpm
磁盘占用 高(每个项目独立) 高(本地缓存) 低(全局共享,可节省 50%+)
安装速度 一般 最快
幽灵依赖 存在 存在 无(严格隔离)
Monorepo 需要工具链辅助 workspace 原生支持
锁文件 package-lock.json yarn.lock pnpm-lock.yaml
主流框架使用 广泛 React 生态 Vite、Vue、Turborepo
安装与初始化
bash 复制代码
# 【命令注释】方式一:通过 Node.js 内置的 corepack 启用(Node 16.10+ 推荐)
corepack enable
corepack prepare pnpm@latest --activate

# 【命令注释】方式二:全局安装
npm install -g pnpm

# 【命令注释】验证安装
pnpm --version
# 输出: 9.x.x

# 【命令注释】初始化项目
pnpm init
# 与 npm init -y 效果相同,生成 package.json
pnpm 常用命令
bash 复制代码
# 【命令注释】安装项目全部依赖
pnpm install          # 等同于 npm install
pnpm i                # 简写

# 【命令注释】添加依赖
pnpm add express                # 生产依赖
pnpm add -D jest typescript     # 开发依赖
pnpm add -g nodemon             # 全局安装

# 【命令注释】删除依赖
pnpm remove express
pnpm remove -g nodemon

# 【命令注释】运行脚本(无需写 run)
pnpm dev              # 等同于 npm run dev
pnpm build
pnpm test

# 【命令注释】一次性执行(等同于 npx)
pnpm dlx create-vite my-app    # 不安装到全局,执行完即删
pnpm create vite my-app        # 简写

# 【命令注释】查看依赖树
pnpm list
pnpm list --depth=0            # 只显示顶层

# 【命令注释】检查过期包
pnpm outdated

# 【命令注释】更新依赖
pnpm update
pnpm update express            # 更新特定包

pnpm vs npm 命令对照表:

操作 npm pnpm
安装所有依赖 npm install pnpm install
添加生产包 npm install pkg pnpm add pkg
添加开发包 npm install -D pkg pnpm add -D pkg
全局安装 npm install -g pkg pnpm add -g pkg
删除包 npm uninstall pkg pnpm remove pkg
运行脚本 npm run dev pnpm dev
一次性执行 npx create-vite pnpm dlx create-vite
按 lock 精确安装 npm ci pnpm install --frozen-lockfile
pnpm Workspace(Monorepo 支持)

Monorepo 是将多个相关项目(如「组件库 + 管理后台 + 文档站」)放在同一 Git 仓库的架构,pnpm workspace 是其最流行的实现方式之一。

目录结构示例:

复制代码
my-monorepo/
├── pnpm-workspace.yaml       # workspace 配置
├── package.json              # 根包(定义共享脚本)
├── packages/
│   ├── ui/                   # 组件库
│   │   └── package.json      # { "name": "@myapp/ui" }
│   └── utils/                # 工具函数库
│       └── package.json      # { "name": "@myapp/utils" }
└── apps/
    ├── web/                  # 前端应用
    │   └── package.json
    └── admin/                # 管理后台
        └── package.json
yaml 复制代码
# 【代码注释】pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'apps/*'
bash 复制代码
# 【命令注释】在特定子包中运行命令
pnpm --filter @myapp/ui build
pnpm --filter @myapp/api dev

# 【命令注释】在所有子包中递归运行
pnpm -r build
pnpm -r test

# 【命令注释】子包之间互相引用(workspace 协议)
# apps/web/package.json:
# "dependencies": { "@myapp/utils": "workspace:*" }
# 安装时 pnpm 会建立符号链接,无需发布即可本地引用

【代码注释】

  • pnpm 全局存储默认在 ~/.local/share/pnpm/store(Linux/macOS);100 个项目引用同版本 lodash 只占一份磁盘。
  • pnpm dlx 等价于 npx,临时下载执行后清理,不污染全局;Vite 生态推荐 pnpm create vite
  • Monorepo 中 workspace:* 协议让子包可以互相依赖本地最新代码,发布时 pnpm 自动将其替换为实际版本号。
  • 切换到 pnpm 的第一步:删除 node_modules 和原有 lock 文件,执行 pnpm install 生成 pnpm-lock.yaml

4. 发布 NPM 包

发布自己的 npm 包可以让其他开发者使用你的代码,这是开源社区贡献的重要方式。

4.1 发布普通包的步骤

名词解释:npm 包发布

npm 包发布是指将开发的模块上传到 npm 仓库,使其他开发者可以通过 npm install 命令安装使用。
渲染错误: Mermaid 渲染失败: Parse error on line 17: ... style Dev fill:#e1f5e1 style N ----------------------^ Expecting '()', 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'SOLID_ARROW_TOP', 'SOLID_ARROW_BOTTOM', 'STICK_ARROW_TOP', 'STICK_ARROW_BOTTOM', 'SOLID_ARROW_TOP_DOTTED', 'SOLID_ARROW_BOTTOM_DOTTED', 'STICK_ARROW_TOP_DOTTED', 'STICK_ARROW_BOTTOM_DOTTED', 'SOLID_ARROW_TOP_REVERSE', 'SOLID_ARROW_BOTTOM_REVERSE', 'STICK_ARROW_TOP_REVERSE', 'STICK_ARROW_BOTTOM_REVERSE', 'SOLID_ARROW_TOP_REVERSE_DOTTED', 'SOLID_ARROW_BOTTOM_REVERSE_DOTTED', 'STICK_ARROW_TOP_REVERSE_DOTTED', 'STICK_ARROW_BOTTOM_REVERSE_DOTTED', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT'

详细发布步骤

第一步:本地开发包内容

bash 复制代码
# 【命令注释】1. 创建项目目录
mkdir my-awesome-package
cd my-awesome-package

# 【命令注释】2. 初始化项目
npm init -y

# 【命令注释】3. 编辑 package.json
json 复制代码
{
  "name": "my-awesome-package",      // 【代码注释】确保包名唯一
  "version": "1.0.0",                // 【代码注释】版本号
  "description": "An awesome utility package",  // 【代码注释】包描述
  "main": "index.js",                // 【代码注释】入口文件
  "keywords": ["utility", "helper", "tools"],  // 【代码注释】关键词
  "author": "Your Name <your.email@example.com>",
  "license": "MIT",                  // 【代码注释】开源协议
  "repository": {                    // 【代码注释】仓库地址
    "type": "git",
    "url": "https://github.com/your-username/my-awesome-package.git"
  },
  "files": [                         // 【代码注释】包含的文件
    "index.js",
    "lib/",
    "README.md"
  ]
}
javascript 复制代码
// 【代码注释】4. 创建包的主要功能(index.js)

/**
 * 【代码注释】字符串处理工具函数
 * @param {string} str - 要处理的字符串
 * @returns {string} 处理后的字符串
 */
function capitalize(str) {
    if (!str || typeof str !== 'string') {
        return '';
    }
    return str.charAt(0).toUpperCase() + str.slice(1);
}

/**
 * 【代码注释】反转字符串
 * @param {string} str - 要反转的字符串
 * @returns {string} 反转后的字符串
 */
function reverse(str) {
    if (!str || typeof str !== 'string') {
        return '';
    }
    return str.split('').reverse().join('');
}

/**
 * 【代码注释】生成随机字符串
 * @param {number} length - 字符串长度
 * @returns {string} 随机字符串
 */
function randomString(length = 10) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let result = '';
    for (let i = 0; i < length; i++) {
        result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
}

// 【代码注释】导出函数
module.exports = {
    capitalize,
    reverse,
    randomString
};
javascript 复制代码
// 【代码注释】5. 创建测试文件(test.js)
const MyPackage = require('./index.js');

console.log('=== 测试字符串工具包 ===');

// 测试 capitalize
console.log('测试 capitalize:');
console.log(MyPackage.capitalize('hello'));  // 输出: Hello
console.log(MyPackage.capitalize(''));       // 输出: (空字符串)
console.log(MyPackage.capitalize(null));     // 输出: (空字符串)

// 测试 reverse
console.log('\n测试 reverse:');
console.log(MyPackage.reverse('hello'));    // 输出: olleh

// 测试 randomString
console.log('\n测试 randomString:');
console.log(MyPackage.randomString(10));    // 输出: 10位随机字符串
console.log(MyPackage.randomString(20));    // 输出: 20位随机字符串

第二步:注册账号并登录

bash 复制代码
# 【命令注释】1. 访问 https://www.npmjs.com/ 注册账号

# 【命令注释】2. 命令行登录
npm login
# 【代码注释】依次输入:
# - Username: (用户名)
# - Password: (密码)
# - Email: (邮箱地址)
# 【代码注释】登录成功会显示:Logged in as username on https://registry.npmjs.org/.

# 【命令注释】3. 验证登录状态
npm whoami
# 输出: username

# 【命令注释】4. 如果使用了镜像源,需要切换回官方源
npm config set registry https://registry.npmjs.org/

第三步:发布包

bash 复制代码
# 【命令注释】1. 检查包名是否已被占用
npm view my-awesome-package
# 【代码注释】如果显示 404,说明包名可用

# 【命令注释】2. 发布包
npm publish
# 【代码注释】发布成功会显示:
# + my-awesome-package@1.0.0
# 【代码注释】包已成功发布到 npm 仓库

# 【命令注释】3. 验证发布
npm view my-awesome-package
# 【代码注释】会显示包的详细信息

第四步:更新包

bash 复制代码
# 【命令注释】1. 修改代码后,更新版本号
# 方式一:手动修改 package.json 中的 version
# 方式二:使用 npm version 命令

npm version patch    # 1.0.0 -> 1.0.1 (修复 bug)
npm version minor    # 1.0.1 -> 1.1.0 (新增功能)
npm version major    # 1.1.0 -> 2.0.0 (破坏性更新)

# 【命令注释】2. 重新发布
npm publish

# 【代码注释】3. 查看已发布的版本
npm view my-awesome-package versions

发布注意事项:

javascript 复制代码
// 【代码注释】1. package.json 必填字段
{
  "name": "unique-package-name",     // 【代码注释】包名必须唯一
  "version": "1.0.0",                // 【代码注释】版本号必须符合语义化版本
  "description": "Package description",  // 【代码注释】包描述
  "main": "index.js"                 // 【代码注释】入口文件
}

// 【代码注释】2. 避免 .npmignore 忽略重要文件
// .npmignore 示例:
node_modules/
test/
*.test.js
.git/
.DS_Store

// 【代码注释】3. 确保 README.md 完整
// README.md 应包含:
// - 项目介绍
// - 安装方法
// - 使用示例
// - API 文档
// - 许可证信息

【代码注释】

  • 发布流程:npm init → 写 main/files → 本地 node test.js 验证 → npm loginregistry 切官方源 → npm publish
  • npm view 包名 返回 404 表示包名可用;已占用需改名或申请作用域 @你的组织/包名
  • npm version patch|minor|major 会改 package.json 并打 Git tag(若在 Git 仓库),再 npm publish 发新版本。
  • 勿把 .env、密钥、node_modules 打进包;用 files 白名单或 .npmignore 控制上传内容。

4.2 发布全局命令工具

发布全局命令工具可以让用户直接在命令行中使用你的工具。

创建可执行命令

第一步:创建命令行脚本

javascript 复制代码
// 【代码注释】bin/cli.js
#!/usr/bin/env node
// 【代码注释】上面这行是 shebang,告诉系统用 node 执行

const fs = require('fs');
const path = require('path');

// 【代码注释】命令行参数处理
const args = process.argv.slice(2);
const command = args[0];

// 【代码注释】显示帮助信息
function showHelp() {
    console.log(`
文件大小转换工具
使用方法:
  fileconv <文件路径>              显示文件大小
  fileconv -h, --help             显示帮助信息
  fileconv -v, --version          显示版本信息
示例:
  fileconv ./large-file.txt
  fileconv /path/to/document.pdf
    `);
}

// 【代码注释】显示版本信息
function showVersion() {
    const packageJson = require('../package.json');
    console.log(`fileconv v${packageJson.version}`);
}

// 【代码注释】转换文件大小
function convertFileSize(filePath) {
    try {
        // 【代码注释】获取文件信息
        const stats = fs.statSync(filePath);
        const bytes = stats.size;
        
        // 【代码注释】转换单位
        const units = ['B', 'KB', 'MB', 'GB', 'TB'];
        let size = bytes;
        let unitIndex = 0;
        
        while (size >= 1024 && unitIndex < units.length - 1) {
            size /= 1024;
            unitIndex++;
        }
        
        // 【代码注释】输出结果
        console.log(`文件: ${path.basename(filePath)}`);
        console.log(`大小: ${size.toFixed(2)} ${units[unitIndex]}`);
        console.log(`字节: ${bytes} bytes`);
        
    } catch (error) {
        console.error(`错误: ${error.message}`);
        process.exit(1);
    }
}

// 【代码注释】命令处理
switch (command) {
    case '-h':
    case '--help':
        showHelp();
        break;
    case '-v':
    case '--version':
        showVersion();
        break;
    default:
        if (!command) {
            showHelp();
        } else {
            convertFileSize(command);
        }
        break;
}

第二步:配置 package.json

json 复制代码
{
  "name": "file-size-converter",
  "version": "1.0.0",
  "description": "A command-line tool to convert file sizes",
  "main": "index.js",
  "bin": {
    "fileconv": "./bin/cli.js"
  },
  "keywords": [
    "file",
    "size",
    "converter",
    "cli",
    "command-line"
  ],
  "author": "Your Name",
  "license": "MIT",
  "preferGlobal": true,
  "engines": {
    "node": ">=14.0.0"
  }
}

第三步:测试和发布

bash 复制代码
# 【命令注释】1. 本地测试(在项目目录下)
npm link
# 【代码注释】这会在全局创建一个符号链接

# 【命令注释】2. 测试命令
fileconv --help
fileconv --version
fileconv ./large-file.txt

# 【命令注释】3. 取消链接
npm unlink -g file-size-converter

# 【命令注释】4. 发布到 npm
npm publish

用户安装和使用:

bash 复制代码
# 【命令注释】用户全局安装
npm install -g file-size-converter

# 【命令注释】使用命令
fileconv --help
fileconv ./my-document.pdf
fileconv /path/to/large/video.mp4

# 【命令注释】卸载命令
npm uninstall -g file-size-converter
高级命令行工具开发

使用 Commander.js:

javascript 复制代码
// 【代码注释】使用 commander.js 创建更强大的命令行工具

const { Command } = require('commander');
const fs = require('fs');
const path = require('path');

const program = new Command();

program
    .name('fileconv')
    .description('File size converter tool')
    .version('1.0.0');

// 【代码注释】转换命令
program
    .command('convert <file>')
    .description('Convert file size to human-readable format')
    .option('-u, --unit <unit>', 'Specify output unit (B, KB, MB, GB, TB)')
    .action((file, options) => {
        try {
            const stats = fs.statSync(file);
            let bytes = stats.size;
            const units = ['B', 'KB', 'MB', 'GB', 'TB'];
            
            let targetUnit = options.unit ? options.unit.toUpperCase() : null;
            let targetIndex = targetUnit ? units.indexOf(targetUnit) : -1;
            
            // 【代码注释】如果没有指定单位或单位无效,自动选择
            if (targetIndex === -1) {
                let size = bytes;
                let unitIndex = 0;
                
                while (size >= 1024 && unitIndex < units.length - 1) {
                    size /= 1024;
                    unitIndex++;
                }
                
                console.log(`${path.basename(file)}: ${size.toFixed(2)} ${units[unitIndex]}`);
            } else {
                // 【代码注释】转换为指定单位
                let size = bytes;
                for (let i = 0; i < targetIndex; i++) {
                    size /= 1024;
                }
                console.log(`${path.basename(file)}: ${size.toFixed(2)} ${units[targetIndex]}`);
            }
            
        } catch (error) {
            console.error(`Error: ${error.message}`);
            process.exit(1);
        }
    });

// 【代码注释】批量转换命令
program
    .command('batch <pattern>')
    .description('Convert multiple files matching a pattern')
    .action((pattern) => {
        const glob = require('glob');
        const files = glob.sync(pattern);
        
        console.log(`Found ${files.length} files:\n`);
        
        files.forEach(file => {
            try {
                const stats = fs.statSync(file);
                let bytes = stats.size;
                const units = ['B', 'KB', 'MB', 'GB', 'TB'];
                
                let size = bytes;
                let unitIndex = 0;
                
                while (size >= 1024 && unitIndex < units.length - 1) {
                    size /= 1024;
                    unitIndex++;
                }
                
                console.log(`${file}: ${size.toFixed(2)} ${units[unitIndex]}`);
                
            } catch (error) {
                console.error(`${file}: Error - ${error.message}`);
            }
        });
    });

program.parse();

package.json 配置:

json 复制代码
{
  "name": "fileconv-pro",
  "version": "1.0.0",
  "description": "Advanced file size converter tool",
  "main": "index.js",
  "bin": {
    "fileconv": "./bin/cli.js"
  },
  "dependencies": {
    "commander": "^11.0.0",
    "glob": "^10.3.0"
  },
  "keywords": [
    "file",
    "size",
    "converter",
    "cli",
    "batch"
  ],
  "author": "Your Name",
  "license": "MIT"
}

使用示例:

bash 复制代码
# 【命令注释】安装后使用
npm install -g fileconv-pro

# 【命令注释】单个文件转换
fileconv convert document.pdf
fileconv convert video.mp4 --unit MB

# 【命令注释】批量转换
fileconv batch "./src/**/*.js"
fileconv batch "images/*.png"

# 【命令注释】查看版本
fileconv --version

5. HTTP 协议详解

HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最广泛的协议,理解 HTTP 协议对于 Web 开发至关重要。

5.1 HTTP 协议概述

名词解释:HTTP 协议

HTTP 是基于 TCP/IP 协议的应用层通信协议,规定了客户端和服务器之间如何通信、传输数据。它是万维网(WWW)数据通信的基础。
#mermaid-svg-rE3WBZjyFhqZPbd9{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-rE3WBZjyFhqZPbd9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rE3WBZjyFhqZPbd9 .error-icon{fill:#552222;}#mermaid-svg-rE3WBZjyFhqZPbd9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rE3WBZjyFhqZPbd9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rE3WBZjyFhqZPbd9 .marker.cross{stroke:#333333;}#mermaid-svg-rE3WBZjyFhqZPbd9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rE3WBZjyFhqZPbd9 p{margin:0;}#mermaid-svg-rE3WBZjyFhqZPbd9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rE3WBZjyFhqZPbd9 .cluster-label text{fill:#333;}#mermaid-svg-rE3WBZjyFhqZPbd9 .cluster-label span{color:#333;}#mermaid-svg-rE3WBZjyFhqZPbd9 .cluster-label span p{background-color:transparent;}#mermaid-svg-rE3WBZjyFhqZPbd9 .label text,#mermaid-svg-rE3WBZjyFhqZPbd9 span{fill:#333;color:#333;}#mermaid-svg-rE3WBZjyFhqZPbd9 .node rect,#mermaid-svg-rE3WBZjyFhqZPbd9 .node circle,#mermaid-svg-rE3WBZjyFhqZPbd9 .node ellipse,#mermaid-svg-rE3WBZjyFhqZPbd9 .node polygon,#mermaid-svg-rE3WBZjyFhqZPbd9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rE3WBZjyFhqZPbd9 .rough-node .label text,#mermaid-svg-rE3WBZjyFhqZPbd9 .node .label text,#mermaid-svg-rE3WBZjyFhqZPbd9 .image-shape .label,#mermaid-svg-rE3WBZjyFhqZPbd9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-rE3WBZjyFhqZPbd9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rE3WBZjyFhqZPbd9 .rough-node .label,#mermaid-svg-rE3WBZjyFhqZPbd9 .node .label,#mermaid-svg-rE3WBZjyFhqZPbd9 .image-shape .label,#mermaid-svg-rE3WBZjyFhqZPbd9 .icon-shape .label{text-align:center;}#mermaid-svg-rE3WBZjyFhqZPbd9 .node.clickable{cursor:pointer;}#mermaid-svg-rE3WBZjyFhqZPbd9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rE3WBZjyFhqZPbd9 .arrowheadPath{fill:#333333;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rE3WBZjyFhqZPbd9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rE3WBZjyFhqZPbd9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rE3WBZjyFhqZPbd9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rE3WBZjyFhqZPbd9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rE3WBZjyFhqZPbd9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rE3WBZjyFhqZPbd9 .cluster text{fill:#333;}#mermaid-svg-rE3WBZjyFhqZPbd9 .cluster span{color:#333;}#mermaid-svg-rE3WBZjyFhqZPbd9 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-rE3WBZjyFhqZPbd9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rE3WBZjyFhqZPbd9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-rE3WBZjyFhqZPbd9 .icon-shape,#mermaid-svg-rE3WBZjyFhqZPbd9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rE3WBZjyFhqZPbd9 .icon-shape p,#mermaid-svg-rE3WBZjyFhqZPbd9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rE3WBZjyFhqZPbd9 .icon-shape .label rect,#mermaid-svg-rE3WBZjyFhqZPbd9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rE3WBZjyFhqZPbd9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rE3WBZjyFhqZPbd9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rE3WBZjyFhqZPbd9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 协议
应用层协议
请求-响应模型
无状态协议
基于 TCP/IP
OSI 模型第7层
客户端发起请求
服务器返回响应
每次请求独立
可靠传输

HTTP 协议特点:

  1. 简单快速:请求方法简单,程序开发方便
  2. 灵活:可以传输任意类型的数据
  3. 无连接:每次连接只处理一个请求
  4. 无状态:不记录客户端的状态信息

HTTP 版本演进:

版本 发布时间 主要特点
HTTP/0.9 1991年 简单的 GET 请求,只支持 HTML
HTTP/1.0 1996年 增加 POST、HEAD 等方法,支持多种数据格式
HTTP/1.1 1997年 持久连接、管道化、分块传输
HTTP/2.0 2015年 多路复用、头部压缩、服务器推送
HTTP/3.0 2022年 基于 QUIC 协议,解决队头阻塞

5.2 请求报文详解

HTTP 请求报文由客户端发送给服务器,包含请求的所有信息。

请求报文结构

#mermaid-svg-clJzt2XQZsephqO2{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-clJzt2XQZsephqO2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-clJzt2XQZsephqO2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-clJzt2XQZsephqO2 .error-icon{fill:#552222;}#mermaid-svg-clJzt2XQZsephqO2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-clJzt2XQZsephqO2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-clJzt2XQZsephqO2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-clJzt2XQZsephqO2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-clJzt2XQZsephqO2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-clJzt2XQZsephqO2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-clJzt2XQZsephqO2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-clJzt2XQZsephqO2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-clJzt2XQZsephqO2 .marker.cross{stroke:#333333;}#mermaid-svg-clJzt2XQZsephqO2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-clJzt2XQZsephqO2 p{margin:0;}#mermaid-svg-clJzt2XQZsephqO2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-clJzt2XQZsephqO2 .cluster-label text{fill:#333;}#mermaid-svg-clJzt2XQZsephqO2 .cluster-label span{color:#333;}#mermaid-svg-clJzt2XQZsephqO2 .cluster-label span p{background-color:transparent;}#mermaid-svg-clJzt2XQZsephqO2 .label text,#mermaid-svg-clJzt2XQZsephqO2 span{fill:#333;color:#333;}#mermaid-svg-clJzt2XQZsephqO2 .node rect,#mermaid-svg-clJzt2XQZsephqO2 .node circle,#mermaid-svg-clJzt2XQZsephqO2 .node ellipse,#mermaid-svg-clJzt2XQZsephqO2 .node polygon,#mermaid-svg-clJzt2XQZsephqO2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-clJzt2XQZsephqO2 .rough-node .label text,#mermaid-svg-clJzt2XQZsephqO2 .node .label text,#mermaid-svg-clJzt2XQZsephqO2 .image-shape .label,#mermaid-svg-clJzt2XQZsephqO2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-clJzt2XQZsephqO2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-clJzt2XQZsephqO2 .rough-node .label,#mermaid-svg-clJzt2XQZsephqO2 .node .label,#mermaid-svg-clJzt2XQZsephqO2 .image-shape .label,#mermaid-svg-clJzt2XQZsephqO2 .icon-shape .label{text-align:center;}#mermaid-svg-clJzt2XQZsephqO2 .node.clickable{cursor:pointer;}#mermaid-svg-clJzt2XQZsephqO2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-clJzt2XQZsephqO2 .arrowheadPath{fill:#333333;}#mermaid-svg-clJzt2XQZsephqO2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-clJzt2XQZsephqO2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-clJzt2XQZsephqO2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-clJzt2XQZsephqO2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-clJzt2XQZsephqO2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-clJzt2XQZsephqO2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-clJzt2XQZsephqO2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-clJzt2XQZsephqO2 .cluster text{fill:#333;}#mermaid-svg-clJzt2XQZsephqO2 .cluster span{color:#333;}#mermaid-svg-clJzt2XQZsephqO2 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-clJzt2XQZsephqO2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-clJzt2XQZsephqO2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-clJzt2XQZsephqO2 .icon-shape,#mermaid-svg-clJzt2XQZsephqO2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-clJzt2XQZsephqO2 .icon-shape p,#mermaid-svg-clJzt2XQZsephqO2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-clJzt2XQZsephqO2 .icon-shape .label rect,#mermaid-svg-clJzt2XQZsephqO2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-clJzt2XQZsephqO2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-clJzt2XQZsephqO2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-clJzt2XQZsephqO2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 请求报文
请求行
请求头
空行
请求体
请求方法
请求 URL
协议版本
键值对形式
描述客户端信息
可选
发送给服务器的数据

实际请求示例
http 复制代码
POST /api/v1/products/comments HTTP/1.1
Host: comment.api.163.com
Connection: keep-alive
Content-Length: 342
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: application/json, text/plain, */*
Origin: https://comment.tie.163.com
Referer: https://comment.tie.163.com/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session_id=abc123; user_token=xyz789

content=This+is+a+comment&userId=123&timestamp=1685348594866
① 请求行

组成部分:

javascript 复制代码
// 【代码注释】请求行格式:方法 + URL + 协议版本
POST /api/v1/products/comments HTTP/1.1
// ↑    ↑                        ↑
// 方法  URL                      版本

常见请求方法:

方法 描述 幂等性 可缓存 使用场景
GET 获取资源 查询数据、页面跳转
POST 创建资源 提交表单、上传文件
PUT 更新资源 完整更新资源
PATCH 部分更新 部分更新资源
DELETE 删除资源 删除资源
HEAD 获取头部 检查资源是否存在
OPTIONS 查询选项 跨域预检请求
CONNECT 建立隧道 HTTPS 代理
TRACE 回显请求 诊断测试

GET vs POST 对比:

javascript 复制代码
// 【代码注释】GET 请求示例
// 【代码注释】特点:数据在 URL 中,有长度限制,可被缓存
fetch('https://api.example.com/users?page=1&limit=10', {
    method: 'GET',
    headers: {
        'Accept': 'application/json'
    }
});

// 【代码注释】POST 请求示例
// 【代码注释】特点:数据在请求体中,无长度限制,更安全
fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        name: 'John Doe',
        email: 'john@example.com'
    })
});
② 请求头

请求头包含客户端的环境信息和请求的元数据。

常见请求头详解:

http 复制代码
# 【代码注释】基础信息头
Host: api.example.com              # 【代码注释】服务器域名和端口
Connection: keep-alive             # 【代码注释】连接方式(keep-alive/close)

# 【代码注释】内容协商头
Accept: application/json           # 【代码注释】客户端接受的数据类型
Accept-Encoding: gzip, deflate     # 【代码注释】客户端支持的压缩格式
Accept-Language: zh-CN,zh;q=0.9    # 【代码注释】客户端接受的语言
Accept-Charset: utf-8              # 【代码注释】客户端接受的字符集

# 【代码注释】客户端信息头
User-Agent: Mozilla/5.0...         # 【代码注释】客户端浏览器信息
Referer: https://example.com       # 【代码注释】请求来源页面
Origin: https://example.com        # 【代码注释】请求源(CORS 相关)

# 【代码注释】安全相关头
Cookie: sessionId=abc123;          # 【代码注释】客户端存储的 Cookie
Authorization: Bearer token123     # 【代码注释】身份认证信息

# 【代码注释】内容类型头
Content-Type: application/json    # 【代码注释】请求体的数据类型
Content-Length: 1024               # 【代码注释】请求体的字节长度
Content-Encoding: gzip             # 【代码注释】请求体的压缩方式

# 【代码注释】缓存控制头
Cache-Control: no-cache            # 【代码注释】缓存控制指令
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT  # 【代码注释】缓存验证
If-None-Match: "33a64df551425fcc"  # 【代码注释】ETag 验证

# 【代码注释】条件请求头
If-Match: "33a64df551425fcc"       # 【代码注释】匹配 ETag 才执行
If-None-Match: "33a64df551425fcc"  # 【代码注释】不匹配 ETag 才执行
If-Range: "33a64df551425fcc"       # 【代码注释】范围请求条件

# 【代码注释】特殊用途头
Upgrade: websocket                # 【代码注释】协议升级
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  # 【代码注释】WebSocket 握手
③ 空行

空行用于分隔请求头和请求体,是 HTTP 协议格式的要求。

http 复制代码
Host: example.com
Content-Type: application/json
Content-Length: 25

# 【代码注释】这个空行是必须的,用于分隔请求头和请求体
{"name":"John","age":30}
④ 请求体

请求体包含要发送给服务器的数据,GET 请求通常没有请求体。

不同数据类型的请求体示例:

http 复制代码
# 【代码注释】1. 表单数据 (application/x-www-form-urlencoded)
Content-Type: application/x-www-form-urlencoded

username=john&password=123&email=john@example.com

# 【代码注释】2. JSON 数据 (application/json)
Content-Type: application/json

{
    "username": "john",
    "password": "123",
    "email": "john@example.com"
}

# 【代码注释】3. 文件上传 (multipart/form-data)
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"

john
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="example.txt"
Content-Type: text/plain

This is the file content.
------WebKitFormBoundary7MA4YWxkTrZu0gW--

# 【代码注释】4. XML 数据 (application/xml)
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<user>
    <username>john</username>
    <password>123</password>
</user>

# 【代码注释】5. 纯文本 (text/plain)
Content-Type: text/plain

This is a plain text message.

5.3 响应报文详解

HTTP 响应报文由服务器返回给客户端,包含响应的所有信息。

响应报文结构

#mermaid-svg-usSWJ5vk58igb19x{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-usSWJ5vk58igb19x .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-usSWJ5vk58igb19x .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-usSWJ5vk58igb19x .error-icon{fill:#552222;}#mermaid-svg-usSWJ5vk58igb19x .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-usSWJ5vk58igb19x .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-usSWJ5vk58igb19x .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-usSWJ5vk58igb19x .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-usSWJ5vk58igb19x .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-usSWJ5vk58igb19x .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-usSWJ5vk58igb19x .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-usSWJ5vk58igb19x .marker{fill:#333333;stroke:#333333;}#mermaid-svg-usSWJ5vk58igb19x .marker.cross{stroke:#333333;}#mermaid-svg-usSWJ5vk58igb19x svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-usSWJ5vk58igb19x p{margin:0;}#mermaid-svg-usSWJ5vk58igb19x .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-usSWJ5vk58igb19x .cluster-label text{fill:#333;}#mermaid-svg-usSWJ5vk58igb19x .cluster-label span{color:#333;}#mermaid-svg-usSWJ5vk58igb19x .cluster-label span p{background-color:transparent;}#mermaid-svg-usSWJ5vk58igb19x .label text,#mermaid-svg-usSWJ5vk58igb19x span{fill:#333;color:#333;}#mermaid-svg-usSWJ5vk58igb19x .node rect,#mermaid-svg-usSWJ5vk58igb19x .node circle,#mermaid-svg-usSWJ5vk58igb19x .node ellipse,#mermaid-svg-usSWJ5vk58igb19x .node polygon,#mermaid-svg-usSWJ5vk58igb19x .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-usSWJ5vk58igb19x .rough-node .label text,#mermaid-svg-usSWJ5vk58igb19x .node .label text,#mermaid-svg-usSWJ5vk58igb19x .image-shape .label,#mermaid-svg-usSWJ5vk58igb19x .icon-shape .label{text-anchor:middle;}#mermaid-svg-usSWJ5vk58igb19x .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-usSWJ5vk58igb19x .rough-node .label,#mermaid-svg-usSWJ5vk58igb19x .node .label,#mermaid-svg-usSWJ5vk58igb19x .image-shape .label,#mermaid-svg-usSWJ5vk58igb19x .icon-shape .label{text-align:center;}#mermaid-svg-usSWJ5vk58igb19x .node.clickable{cursor:pointer;}#mermaid-svg-usSWJ5vk58igb19x .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-usSWJ5vk58igb19x .arrowheadPath{fill:#333333;}#mermaid-svg-usSWJ5vk58igb19x .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-usSWJ5vk58igb19x .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-usSWJ5vk58igb19x .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-usSWJ5vk58igb19x .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-usSWJ5vk58igb19x .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-usSWJ5vk58igb19x .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-usSWJ5vk58igb19x .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-usSWJ5vk58igb19x .cluster text{fill:#333;}#mermaid-svg-usSWJ5vk58igb19x .cluster span{color:#333;}#mermaid-svg-usSWJ5vk58igb19x 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-usSWJ5vk58igb19x .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-usSWJ5vk58igb19x rect.text{fill:none;stroke-width:0;}#mermaid-svg-usSWJ5vk58igb19x .icon-shape,#mermaid-svg-usSWJ5vk58igb19x .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-usSWJ5vk58igb19x .icon-shape p,#mermaid-svg-usSWJ5vk58igb19x .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-usSWJ5vk58igb19x .icon-shape .label rect,#mermaid-svg-usSWJ5vk58igb19x .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-usSWJ5vk58igb19x .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-usSWJ5vk58igb19x .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-usSWJ5vk58igb19x :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 响应报文
响应行
响应头
空行
响应体
协议版本
状态码
状态描述
键值对形式
描述服务器信息
返回给客户端的数据

实际响应示例
http 复制代码
HTTP/1.1 200 OK
Server: nginx/1.18.0
Content-Type: application/json; charset=utf-8
Content-Length: 1024
Connection: keep-alive
Date: Wed, 24 May 2023 10:30:45 GMT
Cache-Control: max-age=3600
ETag: "33a64df551425fcc55"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
Set-Cookie: sessionId=abc123; HttpOnly; Secure
Access-Control-Allow-Origin: *

{
    "status": "success",
    "data": {
        "users": [
            {"id": 1, "name": "John", "email": "john@example.com"},
            {"id": 2, "name": "Jane", "email": "jane@example.com"}
        ]
    },
    "pagination": {
        "page": 1,
        "limit": 10,
        "total": 100
    }
}
① 响应行

组成部分:

javascript 复制代码
// 【代码注释】响应行格式:协议版本 + 状态码 + 状态描述
HTTP/1.1 200 OK
// ↑        ↑   ↑
// 版本     状态码 状态描述

状态码分类:

类别 状态码 描述
1xx 100-199 信息性响应,请求已接收
2xx 200-299 成功响应,请求已处理
3xx 300-399 重定向,需要进一步操作
4xx 400-499 客户端错误,请求有误
5xx 500-599 服务器错误,服务器故障
② 响应头

响应头包含服务器的环境信息和响应的元数据。

常见响应头详解:

http 复制代码
# 【代码注释】服务器信息头
Server: nginx/1.18.0               # 【代码注释】服务器软件信息
Date: Wed, 24 May 2023 10:30:45 GMT  # 【代码注释】响应时间
X-Powered-By: Express              # 【代码注释】应用框架信息

# 【代码注释】内容协商头
Content-Type: application/json     # 【代码注释】响应体的数据类型
Content-Type: text/html; charset=utf-8  # 【代码注释】类型和字符集
Content-Encoding: gzip             # 【代码注释】响应体的压缩方式
Content-Length: 1024               # 【代码注释】响应体的字节长度
Content-Language: zh-CN            # 【代码注释】响应体的语言
Content-Disposition: attachment; filename="file.txt"  # 【代码注释】下载文件名

# 【代码注释】缓存控制头
Cache-Control: max-age=3600         # 【代码注释】缓存策略
Cache-Control: no-cache             # 【代码注释】不使用缓存
Cache-Control: no-store             # 【代码注释】不存储缓存
Expires: Wed, 24 May 2023 11:30:45 GMT  # 【代码注释】缓存过期时间
ETag: "33a64df551425fcc"           # 【代码注释】资源版本标识
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT  # 【代码注释】最后修改时间
Age: 600                           # 【代码注释】缓存已存在时间

# 【代码注释】安全相关头
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict  # 【代码注释】设置 Cookie
Strict-Transport-Security: max-age=31536000; includeSubDomains  # 【代码注释】强制 HTTPS
X-Frame-Options: DENY              # 【代码注释】防止点击劫持
X-Content-Type-Options: nosniff    # 【代码注释】防止 MIME 类型嗅探
X-XSS-Protection: 1; mode=block    # 【代码注释】XSS 保护
Content-Security-Policy: default-src 'self'  # 【代码注释】内容安全策略

# 【代码注释】跨域相关头
Access-Control-Allow-Origin: *     # 【代码注释】允许的源
Access-Control-Allow-Methods: GET, POST, PUT, DELETE  # 【代码注释】允许的方法
Access-Control-Allow-Headers: Content-Type, Authorization  # 【代码注释】允许的头
Access-Control-Max-Age: 3600        # 【代码注释】预检请求缓存时间

# 【代码注释】位置头
Location: https://example.com/new-location  # 【代码注释】重定向地址
Content-Location: /api/v1/users/123  # 【代码注释】实际资源位置

# 【代码注释】认证头
WWW-Authenticate: Basic realm="Access to staging site"  # 【代码注释】认证要求
Proxy-Authenticate: Basic realm="Proxy authentication"  # 【代码注释】代理认证

# 【代码注释】范围请求头
Accept-Ranges: bytes               # 【代码注释】支持范围请求
Content-Range: bytes 0-1023/2048    # 【代码注释】当前返回的范围
③ 空行

空行用于分隔响应头和响应体,是 HTTP 协议格式的要求。

④ 响应体

响应体包含服务器返回给客户端的实际数据。

不同类型的响应体示例:

http 复制代码
# 【代码注释】1. HTML 页面
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
    <title>Example Page</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>

# 【代码注释】2. JSON 数据
Content-Type: application/json

{
    "status": "success",
    "data": {
        "id": 1,
        "name": "John Doe",
        "email": "john@example.com"
    }
}

# 【代码注释】3. 图片文件
Content-Type: image/jpeg
Content-Length: 24568

[二进制图片数据]

# 【代码注释】4. 文件下载
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="document.pdf"
Content-Length: 102400

[文件二进制数据]

# 【代码注释】5. 错误信息
Content-Type: application/json

{
    "error": {
        "code": 400,
        "message": "Invalid request parameters",
        "details": "The 'email' field is required"
    }
}

5.4 URL 统一资源定位符

名词解释:URL

URL(Uniform Resource Locator,统一资源定位符)是互联网上标准资源的地址,用于定位和访问网络上的资源。

URL 结构详解

渲染错误: Mermaid 渲染失败: Lexical error on line 12. Unrecognized text. ...E1/path/to/resource F --> F1[?key= -----------------------^

URL 示例解析:

复制代码
https://www.example.com:8080/api/v1/users?page=1&limit=10#results
│       │               │    │       │      │              │
│       │               │    │       │      │              └─ 锚点 fragment
│       │               │    │       │      └─ 查询参数 query
│       │               │    │       └─ 路径 path
│       │               │    └─ 端口 port
│       │               └─ 主机 host
└───────┴─ 协议 scheme

各组成部分详解:

javascript 复制代码
// 【代码注释】1. 协议(scheme)
const schemes = {
    'http': '超文本传输协议(默认端口 80)',
    'https': 'HTTP 安全版(默认端口 443)',
    'ftp': '文件传输协议',
    'mailto': '电子邮件地址',
    'file': '本地文件',
    'ws': 'WebSocket 协议',
    'wss': 'WebSocket 安全版'
};

// 【代码注释】2. 主机(host)
const hosts = {
    '域名': 'www.example.com',
    'IP地址': '192.168.1.1',
    '本地': 'localhost',
    '本机IP': '127.0.0.1'
};

// 【代码注释】3. 端口(port)
const ports = {
    'HTTP': '80',
    'HTTPS': '443',
    'FTP': '21',
    'SSH': '22',
    'MySQL': '3306',
    'MongoDB': '27017',
    'Redis': '6379'
};

// 【代码注释】4. 路径(path)
const paths = {
    '绝对路径': '/api/v1/users',
    '相对路径': 'users/profile',
    '根路径': '/',
    '文件路径': '/images/logo.png'
};

// 【代码注释】5. 查询参数(query)
const queryStrings = {
    '单一参数': '?page=1',
    '多个参数': '?page=1&limit=10&sort=desc',
    '编码参数': '?name=John%20Doe&email=john%40example.com',
    '数组参数': '?tags[]=javascript&tags[]=nodejs',
    '对象参数': '?user[name]=John&user[age]=30'
};

// 【代码注释】6. 锚点(fragment)
const fragments = {
    '章节定位': '#chapter1',
    '评论定位': '#comments',
    '结果区域': '#results'
};

URL 编码与解码:

javascript 复制代码
// 【代码注释】URL 编码处理
const url = require('url');

// 原始字符串
const originalString = 'Hello World! 你好世界!@#$%^&*()';

// URL 编码
const encoded = encodeURIComponent(originalString);
console.log(encoded);
// 输出: Hello%20World!%20%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C%EF%BC%81%40%23%24%25%5E%26*()

// URL 解码
const decoded = decodeURIComponent(encoded);
console.log(decoded);
// 输出: Hello World! 你好世界!@#$%^&*()

// 【代码注释】完整的 URL 解析示例
const urlString = 'https://www.example.com:8080/api/v1/users?page=1&limit=10#results';
const parsedUrl = new URL(urlString);

console.log(parsedUrl.protocol);  // https:
console.log(parsedUrl.hostname);  // www.example.com
console.log(parsedUrl.port);      // 8080
console.log(parsedUrl.pathname);  // /api/v1/users
console.log(parsedUrl.search);    // ?page=1&limit=10
console.log(parsedUrl.hash);      // #results

// 【代码注释】解析查询参数
console.log(parsedUrl.searchParams.get('page'));    // 1
console.log(parsedUrl.searchParams.get('limit'));   // 10

5.5 HTTP 状态码详解

名词解释:HTTP 状态码

HTTP 状态码是服务器返回的3位数字代码,用于表示服务器对请求的处理结果。状态码分为5大类,每类表示不同类型的响应。

状态码分类图

#mermaid-svg-yC1LIXJS50v9q9Ev{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-yC1LIXJS50v9q9Ev .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yC1LIXJS50v9q9Ev .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yC1LIXJS50v9q9Ev .error-icon{fill:#552222;}#mermaid-svg-yC1LIXJS50v9q9Ev .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yC1LIXJS50v9q9Ev .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yC1LIXJS50v9q9Ev .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yC1LIXJS50v9q9Ev .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yC1LIXJS50v9q9Ev .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yC1LIXJS50v9q9Ev .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yC1LIXJS50v9q9Ev .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yC1LIXJS50v9q9Ev .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yC1LIXJS50v9q9Ev .marker.cross{stroke:#333333;}#mermaid-svg-yC1LIXJS50v9q9Ev svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yC1LIXJS50v9q9Ev p{margin:0;}#mermaid-svg-yC1LIXJS50v9q9Ev .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yC1LIXJS50v9q9Ev .cluster-label text{fill:#333;}#mermaid-svg-yC1LIXJS50v9q9Ev .cluster-label span{color:#333;}#mermaid-svg-yC1LIXJS50v9q9Ev .cluster-label span p{background-color:transparent;}#mermaid-svg-yC1LIXJS50v9q9Ev .label text,#mermaid-svg-yC1LIXJS50v9q9Ev span{fill:#333;color:#333;}#mermaid-svg-yC1LIXJS50v9q9Ev .node rect,#mermaid-svg-yC1LIXJS50v9q9Ev .node circle,#mermaid-svg-yC1LIXJS50v9q9Ev .node ellipse,#mermaid-svg-yC1LIXJS50v9q9Ev .node polygon,#mermaid-svg-yC1LIXJS50v9q9Ev .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yC1LIXJS50v9q9Ev .rough-node .label text,#mermaid-svg-yC1LIXJS50v9q9Ev .node .label text,#mermaid-svg-yC1LIXJS50v9q9Ev .image-shape .label,#mermaid-svg-yC1LIXJS50v9q9Ev .icon-shape .label{text-anchor:middle;}#mermaid-svg-yC1LIXJS50v9q9Ev .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yC1LIXJS50v9q9Ev .rough-node .label,#mermaid-svg-yC1LIXJS50v9q9Ev .node .label,#mermaid-svg-yC1LIXJS50v9q9Ev .image-shape .label,#mermaid-svg-yC1LIXJS50v9q9Ev .icon-shape .label{text-align:center;}#mermaid-svg-yC1LIXJS50v9q9Ev .node.clickable{cursor:pointer;}#mermaid-svg-yC1LIXJS50v9q9Ev .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yC1LIXJS50v9q9Ev .arrowheadPath{fill:#333333;}#mermaid-svg-yC1LIXJS50v9q9Ev .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yC1LIXJS50v9q9Ev .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yC1LIXJS50v9q9Ev .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yC1LIXJS50v9q9Ev .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yC1LIXJS50v9q9Ev .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yC1LIXJS50v9q9Ev .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yC1LIXJS50v9q9Ev .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yC1LIXJS50v9q9Ev .cluster text{fill:#333;}#mermaid-svg-yC1LIXJS50v9q9Ev .cluster span{color:#333;}#mermaid-svg-yC1LIXJS50v9q9Ev 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-yC1LIXJS50v9q9Ev .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yC1LIXJS50v9q9Ev rect.text{fill:none;stroke-width:0;}#mermaid-svg-yC1LIXJS50v9q9Ev .icon-shape,#mermaid-svg-yC1LIXJS50v9q9Ev .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yC1LIXJS50v9q9Ev .icon-shape p,#mermaid-svg-yC1LIXJS50v9q9Ev .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yC1LIXJS50v9q9Ev .icon-shape .label rect,#mermaid-svg-yC1LIXJS50v9q9Ev .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yC1LIXJS50v9q9Ev .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yC1LIXJS50v9q9Ev .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yC1LIXJS50v9q9Ev :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 状态码
1xx 信息性
2xx 成功
3xx 重定向
4xx 客户端错误
5xx 服务器错误
100 Continue
101 Switching Protocols
200 OK
201 Created
204 No Content
301 Moved Permanently
302 Found
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable

常用状态码详解

1xx 信息性响应(100-199)

状态码 状态描述 说明 使用场景
100 Continue 继续 客户端应继续请求 大文件上传
101 Switching Protocols 切换协议 服务器同意切换协议 HTTP 升级到 WebSocket
102 Processing 处理中 服务器正在处理请求 长时间处理任务

2xx 成功响应(200-299)

状态码 状态描述 说明 使用场景
200 OK 成功 请求成功 GET、POST 请求成功
201 Created 已创建 资源创建成功 POST 创建新资源
202 Accepted 已接受 请求已接受,正在处理 异步任务处理
204 No Content 无内容 请求成功,无返回内容 DELETE 请求成功
206 Partial Content 部分内容 返回部分内容 断点续传、视频分段

3xx 重定向(300-399)

状态码 状态描述 说明 使用场景
301 Moved Permanently 永久移动 资源永久重定向 网站域名变更
302 Found 临时重定向 资源临时重定向 临时维护页面
304 Not Modified 未修改 资源未修改,可使用缓存 缓存验证
307 Temporary Redirect 临时重定向 保持请求方法的重定向 HTTP 到 HTTPS 重定向
308 Permanent Redirect 永久重定向 保持请求方法的永久重定向 RESTful API 重构

4xx 客户端错误(400-499)

状态码 状态描述 说明 使用场景
400 Bad Request 错误请求 请求格式错误 参数验证失败
401 Unauthorized 未授权 缺少身份认证 需要登录访问
403 Forbidden 禁止访问 服务器拒绝请求 权限不足
404 Not Found 未找到 资源不存在 访问不存在的页面
405 Method Not Allowed 方法不允许 请求方法不支持 GET 访问只支持 POST 的接口
408 Request Timeout 请求超时 客户端请求超时 网络连接问题
409 Conflict 冲突 请求与服务器状态冲突 资源已被修改
410 Gone 已删除 资源已被永久删除 API 已废弃
413 Payload Too Large 请求体过大 请求体超过服务器限制 文件上传过大
415 Unsupported Media Type 不支持的媒体类型 Content-Type 不支持 发送 JSON 到只接受 XML 的接口
422 Unprocessable Entity 无法处理的实体 语义错误 业务逻辑验证失败
429 Too Many Requests 请求过多 超过速率限制 API 限流

5xx 服务器错误(500-599)

状态码 状态描述 说明 使用场景
500 Internal Server Error 内部服务器错误 服务器遇到意外情况 程序异常
501 Not Implemented 未实现 服务器不支持该功能 使用了不支持的 HTTP 方法
502 Bad Gateway 网关错误 上游服务器返回无效响应 反向代理配置错误
503 Service Unavailable 服务不可用 服务器暂时无法处理 服务器维护
504 Gateway Timeout 网关超时 上游服务器响应超时 后端服务响应慢
505 HTTP Version Not Supported 不支持的 HTTP 版本 服务器不支持请求的 HTTP 版本 使用了 HTTP/2.0 而服务器只支持 HTTP/1.1

实际应用示例:

javascript 复制代码
// 【代码注释】使用 Fetch API 处理不同状态码
async function fetchData(url) {
    try {
        const response = await fetch(url);
        
        switch (response.status) {
            case 200:
                console.log('请求成功');
                return await response.json();
                
            case 201:
                console.log('资源创建成功');
                return await response.json();
                
            case 204:
                console.log('删除成功,无返回内容');
                return null;
                
            case 301:
            case 302:
                console.log('重定向到:', response.headers.get('Location'));
                return await fetchData(response.headers.get('Location'));
                
            case 400:
                console.error('请求参数错误');
                throw new Error('Bad Request');
                
            case 401:
                console.error('未授权,需要登录');
                throw new Error('Unauthorized');
                
            case 403:
                console.error('权限不足');
                throw new Error('Forbidden');
                
            case 404:
                console.error('资源不存在');
                throw new Error('Not Found');
                
            case 500:
                console.error('服务器内部错误');
                throw new Error('Internal Server Error');
                
            case 503:
                console.error('服务暂时不可用');
                throw new Error('Service Unavailable');
                
            default:
                console.warn('未知状态码:', response.status);
                throw new Error(`Unexpected status: ${response.status}`);
        }
        
    } catch (error) {
        console.error('请求失败:', error);
        throw error;
    }
}

6. 创建 Node.js HTTP 服务

Node.js 内置的 http 模块可以轻松创建 HTTP 服务器,无需额外依赖。

6.1 创建基础 HTTP 服务

核心对象介绍

#mermaid-svg-r1OWxte0dv8DhQVf{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-r1OWxte0dv8DhQVf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-r1OWxte0dv8DhQVf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-r1OWxte0dv8DhQVf .error-icon{fill:#552222;}#mermaid-svg-r1OWxte0dv8DhQVf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-r1OWxte0dv8DhQVf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-r1OWxte0dv8DhQVf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-r1OWxte0dv8DhQVf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-r1OWxte0dv8DhQVf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-r1OWxte0dv8DhQVf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-r1OWxte0dv8DhQVf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-r1OWxte0dv8DhQVf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-r1OWxte0dv8DhQVf .marker.cross{stroke:#333333;}#mermaid-svg-r1OWxte0dv8DhQVf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-r1OWxte0dv8DhQVf p{margin:0;}#mermaid-svg-r1OWxte0dv8DhQVf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-r1OWxte0dv8DhQVf .cluster-label text{fill:#333;}#mermaid-svg-r1OWxte0dv8DhQVf .cluster-label span{color:#333;}#mermaid-svg-r1OWxte0dv8DhQVf .cluster-label span p{background-color:transparent;}#mermaid-svg-r1OWxte0dv8DhQVf .label text,#mermaid-svg-r1OWxte0dv8DhQVf span{fill:#333;color:#333;}#mermaid-svg-r1OWxte0dv8DhQVf .node rect,#mermaid-svg-r1OWxte0dv8DhQVf .node circle,#mermaid-svg-r1OWxte0dv8DhQVf .node ellipse,#mermaid-svg-r1OWxte0dv8DhQVf .node polygon,#mermaid-svg-r1OWxte0dv8DhQVf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-r1OWxte0dv8DhQVf .rough-node .label text,#mermaid-svg-r1OWxte0dv8DhQVf .node .label text,#mermaid-svg-r1OWxte0dv8DhQVf .image-shape .label,#mermaid-svg-r1OWxte0dv8DhQVf .icon-shape .label{text-anchor:middle;}#mermaid-svg-r1OWxte0dv8DhQVf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-r1OWxte0dv8DhQVf .rough-node .label,#mermaid-svg-r1OWxte0dv8DhQVf .node .label,#mermaid-svg-r1OWxte0dv8DhQVf .image-shape .label,#mermaid-svg-r1OWxte0dv8DhQVf .icon-shape .label{text-align:center;}#mermaid-svg-r1OWxte0dv8DhQVf .node.clickable{cursor:pointer;}#mermaid-svg-r1OWxte0dv8DhQVf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-r1OWxte0dv8DhQVf .arrowheadPath{fill:#333333;}#mermaid-svg-r1OWxte0dv8DhQVf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-r1OWxte0dv8DhQVf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-r1OWxte0dv8DhQVf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r1OWxte0dv8DhQVf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-r1OWxte0dv8DhQVf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r1OWxte0dv8DhQVf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-r1OWxte0dv8DhQVf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-r1OWxte0dv8DhQVf .cluster text{fill:#333;}#mermaid-svg-r1OWxte0dv8DhQVf .cluster span{color:#333;}#mermaid-svg-r1OWxte0dv8DhQVf 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-r1OWxte0dv8DhQVf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-r1OWxte0dv8DhQVf rect.text{fill:none;stroke-width:0;}#mermaid-svg-r1OWxte0dv8DhQVf .icon-shape,#mermaid-svg-r1OWxte0dv8DhQVf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r1OWxte0dv8DhQVf .icon-shape p,#mermaid-svg-r1OWxte0dv8DhQVf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-r1OWxte0dv8DhQVf .icon-shape .label rect,#mermaid-svg-r1OWxte0dv8DhQVf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r1OWxte0dv8DhQVf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-r1OWxte0dv8DhQVf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-r1OWxte0dv8DhQVf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 模块对象
http 模块
Server 对象
ClientRequest 对象
ServerResponse 对象
require&(&'http&';
http.createServer&(&)
服务器实例
listen&(port&)
请求对象
获取请求信息
响应对象
设置响应信息

创建简单服务器
javascript 复制代码
// 【代码注释】导入 http 模块
const http = require('http');

// 【代码注释】创建服务器
const server = http.createServer((request, response) => {
    // 【代码注释】request 是 http.IncomingMessage 实例(客户端请求对象)
    // 【代码注释】response 是 http.ServerResponse 实例(服务器响应对象)
    
    // 【代码注释】设置响应头
    response.setHeader('Content-Type', 'text/html; charset=utf-8');
    
    // 【代码注释】设置响应体并结束响应
    response.end('<h1>Hello, Node.js HTTP Server!</h1>');
});

// 【代码注释】监听端口
const PORT = 3000;
server.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}/`);
});

【代码注释】

  • http.createServer(callback) 为每个 HTTP 请求调用一次回调;request(IncomingMessage)只读请求,response(ServerResponse)写响应。
  • 必须先 setHeaderendend(body) 发送正文并结束 响应,同一请求多次 end 会报错。
  • listen(PORT) 绑定端口;默认监听 0.0.0.0,本机访问 http://localhost:3000
  • 这是 Express/Koa 的底层:框架封装了路由、中间件,本质仍是 createServer

完整功能的服务器:

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

// 【代码注释】创建服务器
const server = http.createServer((req, res) => {
    // 【代码注释】获取请求信息
    const method = req.method;
    const parsedUrl = url.parse(req.url, true);
    const pathname = parsedUrl.pathname;
    const query = parsedUrl.query;
    
    console.log(`${method} ${pathname}`);
    console.log('查询参数:', query);
    
    // 【代码注释】设置响应头
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.setHeader('Access-Control-Allow-Origin', '*');
    
    // 【代码注释】根据路径返回不同内容
    if (pathname === '/') {
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end(`
            <!DOCTYPE html>
            <html>
            <head>
                <title>Node.js HTTP Server</title>
                <meta charset="utf-8">
            </head>
            <body>
                <h1>欢迎使用 Node.js HTTP 服务器</h1>
                <p>这是一个简单的 HTTP 服务器示例</p>
                <ul>
                    <li><a href="/api">API 接口</a></li>
                    <li><a href="/time">当前时间</a></li>
                    <li><a href="/user?id=1">用户信息</a></li>
                </ul>
            </body>
            </html>
        `);
        
    } else if (pathname === '/api') {
        res.writeHead(200);
        res.end(JSON.stringify({
            status: 'success',
            message: 'API 接口正常工作',
            timestamp: Date.now()
        }));
        
    } else if (pathname === '/time') {
        res.writeHead(200);
        res.end(JSON.stringify({
            datetime: new Date().toISOString(),
            timestamp: Date.now(),
            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
        }));
        
    } else if (pathname === '/user') {
        const userId = query.id;
        if (userId) {
            res.writeHead(200);
            res.end(JSON.stringify({
                id: userId,
                name: `User ${userId}`,
                email: `user${userId}@example.com`
            }));
        } else {
            res.writeHead(400);
            res.end(JSON.stringify({
                error: '缺少用户ID参数'
            }));
        }
        
    } else {
        res.writeHead(404);
        res.end(JSON.stringify({
            error: '页面不存在',
            path: pathname
        }));
    }
});

// 【代码注释】启动服务器
const PORT = 3000;
server.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}/`);
    console.log(`访问 http://localhost:${PORT}/api 查看 API 接口`);
    console.log(`访问 http://localhost:${PORT}/time 查看当前时间`);
});

【代码注释】

  • url.parse(req.url, true) 第二参数 truequery 解析为对象(如 ?id=1{ id: '1' }),与 §2.3 URL / §2.4 querystring 对应。
  • writeHead(200) 可一次写入状态码与头;若已 setHeader,注意勿重复冲突。
  • 路由用 pathname 分支,是 Express app.get('/api') 的雏形;/userid 时返回 400 演示客户端错误。
  • Access-Control-Allow-Origin: * 便于 §7.4 的 fetch 联调;生产应限制来源。
端口占用处理
javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
    res.end('Hello, World!');
});

const PORT = 3000;

server.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}/`);
}).on('error', (err) => {
    if (err.code === 'EADDRINUSE') {
        console.error(`端口 ${PORT} 已被占用`);
        console.error('解决方案:');
        console.error('1. 更换端口:修改 PORT 变量的值');
        console.error('2. 关闭占用端口的程序');
        console.error('3. 查找占用端口的进程:');
        console.error(`   - macOS/Linux: lsof -i :${PORT}`);
        console.error(`   - Windows: netstat -ano | findstr :${PORT}`);
    } else {
        console.error('服务器错误:', err);
    }
});

6.2 获取请求报文信息

获取请求行信息
javascript 复制代码
const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
    // 【代码注释】获取 HTTP 版本
    const httpVersion = req.httpVersion;
    console.log('HTTP 版本:', httpVersion);  // 1.1
    
    // 【代码注释】获取请求 URL
    const requestUrl = req.url;
    console.log('请求 URL:', requestUrl);  // /path?query=value
    
    // 【代码注释】获取请求方法
    const method = req.method;
    console.log('请求方法:', method);  // GET, POST, PUT, DELETE 等
    
    // 【代码注释】解析 URL
    const parsedUrl = url.parse(req.url, true);
    console.log('路径:', parsedUrl.pathname);  // /path
    console.log('查询参数:', parsedUrl.query);  // { query: 'value' }
    
    res.end('请求信息已记录到控制台');
});

server.listen(3000);
获取请求头信息
javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
    // 【代码注释】获取所有请求头
    const headers = req.headers;
    console.log('所有请求头:', headers);
    
    // 【代码注释】获取特定的请求头
    const userAgent = req.headers['user-agent'];
    const contentType = req.headers['content-type'];
    const authorization = req.headers['authorization'];
    const accept = req.headers['accept'];
    const cookie = req.headers['cookie'];
    
    console.log('User-Agent:', userAgent);
    console.log('Content-Type:', contentType);
    console.log('Authorization:', authorization);
    console.log('Accept:', accept);
    console.log('Cookie:', cookie);
    
    // 【代码注释】处理不同类型的请求
    if (contentType && contentType.includes('application/json')) {
        console.log('客户端发送的是 JSON 数据');
    } else if (contentType && contentType.includes('application/x-www-form-urlencoded')) {
        console.log('客户端发送的是表单数据');
    }
    
    res.end('请求头信息已记录到控制台');
});

server.listen(3000);
获取客户端 IP 地址
javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
    // 【代码注释】获取客户端 IP 地址
    let clientIp = req.socket.remoteAddress;
    
    // 【代码注释】处理 IPv4 映射的 IPv6 地址
    if (clientIp.startsWith('::ffff:')) {
        clientIp = clientIp.substring(7);
    }
    
    // 【代码注释】如果使用了代理,从请求头获取真实 IP
    const forwardedFor = req.headers['x-forwarded-for'];
    if (forwardedFor) {
        clientIp = forwardedFor.split(',')[0].trim();
    }
    
    console.log('客户端 IP:', clientIp);
    
    // 【代码注释】获取其他连接信息
    const remotePort = req.socket.remotePort;
    const localAddress = req.socket.localAddress;
    const localPort = req.socket.localPort;
    
    console.log('客户端端口:', remotePort);
    console.log('服务器地址:', localAddress);
    console.log('服务器端口:', localPort);
    
    res.end(`您的 IP 地址是: ${clientIp}`);
});

server.listen(3000);
获取 URL 查询字符串
javascript 复制代码
const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
    // 【代码注释】解析 URL
    const parsedUrl = url.parse(req.url, true);
    
    // 【代码注释】获取路径名
    const pathname = parsedUrl.pathname;
    console.log('路径名:', pathname);
    
    // 【代码注释】获取查询参数对象
    const query = parsedUrl.query;
    console.log('查询参数:', query);
    
    // 【代码注释】访问特定查询参数
    const page = parseInt(query.page) || 1;
    const limit = parseInt(query.limit) || 10;
    const keyword = query.keyword || '';
    
    console.log('页码:', page);
    console.log('每页数量:', limit);
    console.log('关键词:', keyword);
    
    // 【代码注释】构建响应
    const response = {
        pathname: pathname,
        params: {
            page: page,
            limit: limit,
            keyword: keyword
        },
        data: [`Item 1`, `Item 2`, `Item ${page}`]
    };
    
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(response, null, 2));
});

server.listen(3000);

// 【代码注释】测试 URL:http://localhost:3000/search?page=2&limit=5&keyword=test
获取请求体信息
javascript 复制代码
const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
    // 【代码注释】处理 POST 请求
    if (req.method === 'POST') {
        let body = '';
        
        // 【代码注释】监听 data 事件,接收数据块
        req.on('data', (chunk) => {
            body += chunk.toString();
            console.log('接收数据块:', chunk.length, '字节');
        });
        
        // 【代码注释】监听 end 事件,数据接收完成
        req.on('end', () => {
            console.log('完整请求体:', body);
            console.log('请求体长度:', body.length);
            
            // 【代码注释】根据 Content-Type 解析数据
            const contentType = req.headers['content-type'];
            
            if (contentType && contentType.includes('application/json')) {
                // 【代码注释】解析 JSON 数据
                try {
                    const jsonData = JSON.parse(body);
                    console.log('JSON 数据:', jsonData);
                    
                    res.setHeader('Content-Type', 'application/json');
                    res.end(JSON.stringify({
                        status: 'success',
                        received: jsonData
                    }));
                } catch (error) {
                    res.writeHead(400, { 'Content-Type': 'application/json' });
                    res.end(JSON.stringify({ error: 'Invalid JSON' }));
                }
                
            } else if (contentType && contentType.includes('application/x-www-form-urlencoded')) {
                // 【代码注释】解析表单数据
                const formData = querystring.parse(body);
                console.log('表单数据:', formData);
                
                res.setHeader('Content-Type', 'application/json');
                res.end(JSON.stringify({
                    status: 'success',
                    received: formData
                }));
                
            } else {
                // 【代码注释】原始文本数据
                console.log('原始数据:', body);
                
                res.setHeader('Content-Type', 'text/plain');
                res.end(`接收到的数据: ${body}`);
            }
        });
        
    } else {
        // 【代码注释】处理 GET 请求
        res.setHeader('Content-Type', 'text/html');
        res.end(`
            <!DOCTYPE html>
            <html>
            <head>
                <title>POST 测试</title>
                <meta charset="utf-8">
            </head>
            <body>
                <h1>POST 请求测试</h1>
                <form method="POST" action="/">
                    <div>
                        <label>用户名:</label>
                        <input type="text" name="username" required>
                    </div>
                    <div>
                        <label>邮箱:</label>
                        <input type="email" name="email" required>
                    </div>
                    <div>
                        <label>消息:</label>
                        <textarea name="message" rows="4" required></textarea>
                    </div>
                    <button type="submit">提交</button>
                </form>
            </body>
            </html>
        `);
    }
});

server.listen(3000);

6.3 设置响应报文

设置响应行
javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
    // 【代码注释】设置响应状态码
    res.statusCode = 200;
    
    // 【代码注释】设置响应状态描述
    res.statusMessage = 'OK';
    
    // 【代码注释】使用 writeHead 同时设置状态码、状态描述和响应头
    res.writeHead(200, 'OK', {
        'Content-Type': 'text/plain',
        'Custom-Header': 'Custom-Value'
    });
    
    res.end('响应设置完成');
});

server.listen(3000);
设置响应头
javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
    // 【代码注释】设置单个响应头
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Access-Control-Allow-Origin', '*');
    
    // 【代码注释】设置多个相同名称的响应头
    res.setHeader('Set-Cookie', [
        'sessionId=abc123; HttpOnly; Secure',
        'theme=dark; Max-Age=3600'
    ]);
    
    // 【代码注释】获取已设置的响应头
    const contentType = res.getHeader('Content-Type');
    console.log('Content-Type:', contentType);
    
    // 【代码注释】删除响应头
    res.removeHeader('Cache-Control');
    
    // 【代码注释】检查是否已发送响应头
    if (!res.headersSent) {
        console.log('响应头尚未发送');
        res.setHeader('X-Custom-Header', 'Custom-Value');
    }
    
    // 【代码注释】发送响应
    res.end(JSON.stringify({ message: '响应头设置完成' }));
});

server.listen(3000);
设置响应体
javascript 复制代码
const http = require('http');
const fs = require('fs');

const server = http.createServer((req, res) => {
    // 【代码注释】设置响应头
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    
    // 【代码注释】使用 write() 方法多次写入响应体
    res.write('<!DOCTYPE html>\n');
    res.write('<html>\n');
    res.write('<head>\n');
    res.write('    <title>Node.js HTTP Server</title>\n');
    res.write('    <meta charset="utf-8">\n');
    res.write('</head>\n');
    res.write('<body>\n');
    res.write('    <h1>欢迎使用 Node.js HTTP 服务器</h1>\n');
    res.write('    <p>这是一个使用 write() 方法构建的页面</p>\n');
    res.write('</body>\n');
    res.write('</html>\n');
    
    // 【代码注释】结束响应
    res.end();
});

// 【代码注释】流式传输大文件
const fileServer = http.createServer((req, res) => {
    const filePath = './large-file.html';
    
    // 【代码注释】设置响应头
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    
    // 【代码注释】创建文件读取流
    const readStream = fs.createReadStream(filePath);
    
    // 【代码注释】将文件流通过响应流发送
    readStream.pipe(res);
    
    // 【代码注释】处理错误
    readStream.on('error', (err) => {
        res.statusCode = 500;
        res.end('文件读取失败');
    });
});

// 【代码注释】下载文件
const downloadServer = http.createServer((req, res) => {
    const filePath = './example.pdf';
    const fileName = 'downloaded-file.pdf';
    
    // 【代码注释】设置文件下载响应头
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
    
    // 【代码注释】创建文件读取流
    const readStream = fs.createReadStream(filePath);
    
    // 【代码注释】流式传输文件
    readStream.pipe(res);
    
    readStream.on('error', (err) => {
        res.statusCode = 500;
        res.end('文件下载失败');
    });
});

// 【代码注释】JSON API 响应
const apiServer = http.createServer((req, res) => {
    // 【代码注释】设置 JSON 响应头
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.setHeader('Access-Control-Allow-Origin', '*');
    
    // 【代码注释】根据请求路径返回不同数据
    const url = new URL(req.url, `http://${req.headers.host}`);
    const pathname = url.pathname;
    
    if (pathname === '/api/users') {
        // 【代码注释】返回用户列表
        const users = [
            { id: 1, name: 'Alice', email: 'alice@example.com' },
            { id: 2, name: 'Bob', email: 'bob@example.com' },
            { id: 3, name: 'Charlie', email: 'charlie@example.com' }
        ];
        
        res.statusCode = 200;
        res.end(JSON.stringify({
            status: 'success',
            data: users,
            total: users.length
        }));
        
    } else if (pathname === '/api/error') {
        // 【代码注释】返回错误响应
        res.statusCode = 400;
        res.end(JSON.stringify({
            status: 'error',
            message: '请求参数错误',
            code: 400
        }));
        
    } else {
        // 【代码注释】404 响应
        res.statusCode = 404;
        res.end(JSON.stringify({
            status: 'error',
            message: '接口不存在',
            path: pathname
        }));
    }
});

// 【代码注释】启动不同的服务器
const PORT1 = 3001;
const PORT2 = 3002;
const PORT3 = 3003;

server.listen(PORT1, () => {
    console.log(`基础服务器运行在 http://localhost:${PORT1}/`);
});

fileServer.listen(PORT2, () => {
    console.log(`文件服务器运行在 http://localhost:${PORT2}/`);
});

apiServer.listen(PORT3, () => {
    console.log(`API 服务器运行在 http://localhost:${PORT3}/`);
});
结束响应
javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
    // 【代码注释】方式一:只结束响应
    res.write('Hello, ');
    res.write('World!');
    res.end();  // 【代码注释】结束响应
    
    // 【代码注释】方式二:设置响应体并结束响应
    res.end('Hello, World!');  // 【代码注释】直接结束响应
    
    // 【代码注释】方式三:发送 JSON 数据并结束响应
    const data = {
        message: 'Hello, World!',
        timestamp: Date.now()
    };
    res.end(JSON.stringify(data));
    
    // 【代码注释】检查响应是否已结束
    if (res.writableEnded) {
        console.log('响应已结束');
    }
});

server.listen(3000);

7. 实战案例与最佳实践

7.1 文件大小转换工具

功能介绍

文件大小转换工具可以将字节数转换为人类可读的格式(B、KB、MB、GB、TB),这在文件管理、存储分析等场景中非常实用。

实现代码
javascript 复制代码
// 【代码注释】文件大小转换工具
// 【代码注释】文件名: convertbyte.js

/**
 * 【代码注释】计算机容量单位转换函数
 * @param {number} bytes - 字节数
 * @param {number} type - 转换类型:0:不转换 1:KB 2:MB 3:GB 4:TB,默认值是0
 * @returns {number} 转换之后的结果
 */
const convertByte = (bytes, type = 0) => {
    // 【代码注释】验证输入参数
    if (typeof bytes !== 'number' || bytes < 0) {
        throw new Error('字节数必须是非负数');
    }
    
    if (type < 0 || type > 4) {
        throw new Error('转换类型必须在 0-4 之间');
    }
    
    // 【代码注释】执行转换计算
    return bytes / Math.pow(1024, type);
};

/**
 * 【代码注释】自动选择最佳单位的转换函数
 * @param {number} bytes - 字节数
 * @returns {object} 包含转换结果的详细对象
 */
const autoConvertByte = (bytes) => {
    if (typeof bytes !== 'number' || bytes < 0) {
        throw new Error('字节数必须是非负数');
    }
    
    const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
    let size = bytes;
    let unitIndex = 0;
    
    // 【代码注释】自动选择合适的单位
    while (size >= 1024 && unitIndex < units.length - 1) {
        size /= 1024;
        unitIndex++;
    }
    
    return {
        original: bytes,
        converted: size,
        unit: units[unitIndex],
        formatted: `${size.toFixed(2)} ${units[unitIndex]}`,
        unitIndex: unitIndex
    };
};

/**
 * 【代码注释】格式化文件大小显示
 * @param {number} bytes - 字节数
 * @param {number} decimals - 小数位数,默认2位
 * @returns {string} 格式化后的字符串
 */
const formatFileSize = (bytes, decimals = 2) => {
    if (typeof bytes !== 'number' || bytes < 0) {
        throw new Error('字节数必须是非负数');
    }
    
    if (bytes === 0) return '0 B';
    
    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
    
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
};

// 【代码注释】导出函数
module.exports = {
    convertByte,
    autoConvertByte,
    formatFileSize
};
测试代码
javascript 复制代码
// 【代码注释】测试文件
// 【代码注释】文件名: test.js

const fs = require('fs');
const { convertByte, autoConvertByte, formatFileSize } = require('./convertbyte');

console.log('=== 文件大小转换工具测试 ===\n');

// 【代码注释】测试 convertByte 函数
console.log('1. 测试指定单位转换:');
console.log('1024 字节 =', convertByte(1024, 1), 'KB');
console.log('1048576 字节 =', convertByte(1048576, 2), 'MB');
console.log('1073741824 字节 =', convertByte(1073741824, 3), 'GB');

// 【代码注释】测试 autoConvertByte 函数
console.log('\n2. 测试自动单位转换:');
const sizes = [500, 1024, 1048576, 1073741824, 1099511627776];
sizes.forEach(size => {
    const result = autoConvertByte(size);
    console.log(`${size} 字节 = ${result.formatted}`);
});

// 【代码注释】测试 formatFileSize 函数
console.log('\n3. 测试格式化输出:');
console.log(formatFileSize(500));
console.log(formatFileSize(1024));
console.log(formatFileSize(1048576));
console.log(formatFileSize(1073741824));

// 【代码注释】测试实际文件大小
console.log('\n4. 测试实际文件大小:');
const filename = './large-file.txt';
const stats = fs.statSync(filename);
console.log(`文件大小: ${formatFileSize(stats.size)}`);

// 【代码注释】测试错误处理
console.log('\n5. 测试错误处理:');
try {
    console.log(convertByte(-100));
} catch (error) {
    console.log('捕获错误:', error.message);
}

try {
    console.log(convertByte(100, 10));
} catch (error) {
    console.log('捕获错误:', error.message);
}
使用示例
javascript 复制代码
// 【代码注释】在实际项目中的使用示例

const http = require('http');
const fs = require('fs');
const { formatFileSize } = require('./convertbyte');

const server = http.createServer((req, res) => {
    if (req.url === '/file-info') {
        // 【代码注释】获取文件信息
        const filePath = './example.pdf';
        const stats = fs.statSync(filePath);
        
        // 【代码注释】格式化文件大小
        const formattedSize = formatFileSize(stats.size);
        
        // 【代码注释】返回文件信息
        res.setHeader('Content-Type', 'application/json');
        res.end(JSON.stringify({
            filename: 'example.pdf',
            size: stats.size,
            formattedSize: formattedSize,
            created: stats.birthtime,
            modified: stats.mtime
        }, null, 2));
        
    } else {
        res.setHeader('Content-Type', 'text/html');
        res.end(`
            <!DOCTYPE html>
            <html>
            <head>
                <title>文件信息</title>
                <meta charset="utf-8">
            </head>
            <body>
                <h1>文件信息查询</h1>
                <p>访问 <a href="/file-info">/file-info</a> 查看文件信息</p>
            </body>
            </html>
        `);
    }
});

server.listen(3000, () => {
    console.log('服务器运行在 http://localhost:3000/');
});

7.2 模块路径实践案例

项目结构示例
复制代码
project/
├── data/
│   └── data.txt
├── lib/
│   ├── fileHandler.js
│   └── utils/
│       └── stringUtils.js
├── modules/
│   └── converter/
│       ├── index.js
│       └── helpers.js
└── app.js
代码实现

lib/utils/stringUtils.js

javascript 复制代码
// 【代码注释】字符串工具模块
const stringUtils = {
    // 【代码注释】首字母大写
    capitalize(str) {
        if (!str || typeof str !== 'string') return '';
        return str.charAt(0).toUpperCase() + str.slice(1);
    },
    
    // 【代码注释】反转字符串
    reverse(str) {
        if (!str || typeof str !== 'string') return '';
        return str.split('').reverse().join('');
    },
    
    // 【代码注释】截断字符串
    truncate(str, length) {
        if (!str || typeof str !== 'string') return '';
        return str.length > length ? str.substring(0, length) + '...' : str;
    }
};

module.exports = stringUtils;

lib/fileHandler.js

javascript 复制代码
// 【代码注释】文件处理模块
const fs = require('fs');
const path = require('path');
const stringUtils = require('./utils/stringUtils');

const fileHandler = {
    // 【代码注释】读取文件内容
    readFile(filePath) {
        // 【代码注释】使用模块路径(相对于当前文件)
        const fullPath = path.join(__dirname, filePath);
        
        try {
            const content = fs.readFileSync(fullPath, 'utf-8');
            return content;
        } catch (error) {
            throw new Error(`文件读取失败: ${error.message}`);
        }
    },
    
    // 【代码注释】处理文件内容
    processContent(filePath) {
        const content = this.readFile(filePath);
        const lines = content.split('\n');
        
        return lines.map(line => ({
            original: line,
            capitalized: stringUtils.capitalize(line),
            reversed: stringUtils.reverse(line),
            truncated: stringUtils.truncate(line, 10)
        }));
    }
};

module.exports = fileHandler;

modules/converter/helpers.js

javascript 复制代码
// 【代码注释】转换器辅助函数
const helpers = {
    // 【代码注释】字节转换为 KB
    toKB(bytes) {
        return bytes / 1024;
    },
    
    // 【代码注释】字节转换为 MB
    toMB(bytes) {
        return bytes / (1024 * 1024);
    },
    
    // 【代码注释】字节转换为 GB
    toGB(bytes) {
        return bytes / (1024 * 1024 * 1024);
    },
    
    // 【代码注释】自动选择单位
    autoConvert(bytes) {
        const units = ['B', 'KB', 'MB', 'GB', 'TB'];
        let size = bytes;
        let unitIndex = 0;
        
        while (size >= 1024 && unitIndex < units.length - 1) {
            size /= 1024;
            unitIndex++;
        }
        
        return {
            size: size,
            unit: units[unitIndex],
            string: `${size.toFixed(2)} ${units[unitIndex]}`
        };
    }
};

module.exports = helpers;

modules/converter/index.js

javascript 复制代码
// 【代码注释】转换器主模块
const helpers = require('./helpers');
const fs = require('fs');

const converter = {
    // 【代码注释】文件大小转换
    fileSize(filePath) {
        const stats = fs.statSync(filePath);
        return helpers.autoConvert(stats.size);
    },
    
    // 【代码注释】批量文件大小转换
    batchFileSize(filePaths) {
        return filePaths.map(path => ({
            file: path,
            info: this.fileSize(path)
        }));
    }
};

module.exports = converter;

app.js

javascript 复制代码
// 【代码注释】主应用程序
const fs = require('fs');
const fileHandler = require('./lib/fileHandler');
const converter = require('./modules/converter');

// 【代码注释】使用文件处理模块
console.log('=== 文件处理示例 ===');
try {
    // 【代码注释】注意:这里使用的是文件路径,相对于命令行目录
    const content = fs.readFile('./data/data.txt', 'utf-8');
    console.log('文件内容:', content);
    
    // 【代码注释】使用 fileHandler 模块
    const processed = fileHandler.processContent('../data/data.txt');
    console.log('处理后的内容:', processed);
    
} catch (error) {
    console.log('错误:', error.message);
}

// 【代码注释】使用转换器模块
console.log('\n=== 文件大小转换示例 ===');
const files = [
    './data/data.txt',
    './lib/fileHandler.js',
    './app.js'
];

files.forEach(file => {
    try {
        const result = converter.fileSize(file);
        console.log(`${file}: ${result.string}`);
    } catch (error) {
        console.log(`${file}: 文件不存在`);
    }
});

// 【代码注释】批量转换
console.log('\n=== 批量转换示例 ===');
const batchResults = converter.batchFileSize(files);
batchResults.forEach(result => {
    console.log(`${result.file}: ${result.info.string}`);
});

7.3 核心案例合集(模块 + 发布 CLI)

容量换算 发布为可安装的 NPM 包,并注册全局命令 converbyte,是「库 + CLI」的经典组合。

package.json(节选)

json 复制代码
{
  "name": "converbyte-tool",
  "version": "1.0.0",
  "description": "字节到 KB/MB/GB/TB 的换算工具",
  "main": "index.js",
  "bin": {
    "converbyte": "./exec.js"
  },
  "type": "commonjs",
  "keywords": ["bytes", "converter", "cli"],
  "license": "MIT"
}

【代码注释】

  • bin 对象: 为安装后全局命令名(如 converbyte), 为项目内可执行文件相对路径(如 ./exec.js)。
  • npm install -g .npm link 会在全局 bin 目录创建符号链接,无需先 publish 即可本地调试 CLI。
  • mainrequire('converbyte-tool') 引用库;CLI 与库可共用 index.js 逻辑。
  • 发布前在 package.json 填好 files.npmignore,避免把测试、源码地图上传到 npm。

index.js

javascript 复制代码
const coverByte = (bytes, type = 0) => bytes / 1024 ** type;
module.exports = coverByte;

exec.js

javascript 复制代码
#!/usr/bin/env node
const converbyte = require('./index.js');

const bytes = Number(process.argv[2]);
const type = Number(process.argv[3] ?? 0);

if (Number.isNaN(bytes)) {
    console.error('用法: converbyte <字节数> [类型0-4]');
    process.exit(1);
}

console.log(converbyte(bytes, type));

【代码注释】

  • Shebang #!/usr/bin/env node:在 Unix/macOS 上让系统用 PATH 里的 node 执行本文件;Windows 靠 npm 的 .cmd 包装。
  • process.argv[0] 为 node 路径,[1] 为脚本路径,[2] 起才是用户参数converbyte 1048576 2 → bytes=1048576, type=2)。
  • process.exit(1) 表示异常退出,CI/脚本可据此判断失败。
  • Number(process.argv[3] ?? 0)?? 在未传第三参数时用默认 type=0(字节)。
bash 复制代码
npm link
converbyte 1048576 2
# 输出约 1(MB)

经典场景:运维脚本统计日志目录体积、上传组件显示「已选文件 xx MB」、CI 产物体积门禁。

7.4 浏览器端 HTTP 演示(可运行 HTML)

下面是一个完整 HTML 页面 :先启动上一节的 Node 服务(例如 http://localhost:3000),再在浏览器打开本文件,用 fetch 观察请求/响应与状态码(跨域时服务端需设置 Access-Control-Allow-Origin)。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>HTTP 客户端演示</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 720px; margin: 2rem auto; padding: 0 1rem; }
    button { margin: 0.25rem; padding: 0.5rem 1rem; cursor: pointer; }
    pre { background: #1e1e1e; color: #d4d4d4; padding: 1rem; overflow: auto; border-radius: 8px; }
    .ok { color: #4ec9b0; } .err { color: #f48771; }
  </style>
</head>
<body>
  <h1>HTTP 请求演示</h1>
  <p>请先在本机启动 API:<code>node server.js</code>(监听 3000 端口)</p>
  <button type="button" id="btn-get">GET /api</button>
  <button type="button" id="btn-time">GET /time</button>
  <button type="button" id="btn-404">GET /not-exist</button>
  <pre id="out">点击按钮发送请求...</pre>

  <script>
    const out = document.getElementById('out');
    const base = 'http://localhost:3000';

    async function call(path) {
      out.textContent = '请求中...';
      try {
        const res = await fetch(base + path);
        const text = await res.text();
        out.innerHTML =
          '<span class="' + (res.ok ? 'ok' : 'err') + '">' +
          '状态: ' + res.status + ' ' + res.statusText + '</span>\n' +
          'URL: ' + base + path + '\n\n' + text;
      } catch (e) {
        out.textContent = '请求失败(服务未启动或 CORS): ' + e.message;
      }
    }

    document.getElementById('btn-get').onclick = () => call('/api');
    document.getElementById('btn-time').onclick = () => call('/time');
    document.getElementById('btn-404').onclick = () => call('/not-exist');
  </script>
</body>
</html>

【代码注释】

  • fetch 只有网络失败才 catch;HTTP 404/500 不会抛错 ,需看 res.ok(true 当且仅当状态码 200--299)。
  • res.text() / res.json() 解析响应体;先读 body 再判断业务逻辑是常见模式。
  • 跨域时浏览器先发 OPTIONS 预检;服务端未返回 CORS 头会表现为 catch 里「CORS」错误。
  • 按钮 /not-exist 演示 404,对应 §5.5 状态码表与 Node 里 res.statusCode = 404

配套最小 server.js(与上文 HTML 联调)

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

const server = http.createServer((req, res) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Content-Type', 'application/json; charset=utf-8');

  if (req.url === '/api') {
    res.end(JSON.stringify({ ok: true, msg: 'API 正常' }));
  } else if (req.url === '/time') {
    res.end(JSON.stringify({ time: new Date().toISOString() }));
  } else {
    res.statusCode = 404;
    res.end(JSON.stringify({ error: 'Not Found' }));
  }
});

server.listen(3000, () => console.log('http://localhost:3000'));

【代码注释】

  • Access-Control-Allow-Origin: * 允许任意网页用 fetch 访问本 API,仅限本地演示
  • 生产应改为具体源,如 https://app.example.com,并配合 Access-Control-Allow-Methods、凭证时不能用 *
  • req.url 含路径与查询串;示例只判断路径,未解析 ?id=1(可用 url.parsenew URL)。
  • res.statusCode = 404 须在 end 前设置;JSON 体与 Content-Type: application/json 成对出现。

7.5 完整 RESTful API 案例(纯 Node.js)

不依赖任何第三方框架,用原生 http 模块实现完整的用户 CRUD 接口。这是理解 Express/Koa 路由机制的必备基础------框架帮你做的,底层都在这里。

API 设计
方法 路径 描述 成功状态码
GET /api/users 获取所有用户(支持 ?role= 过滤) 200
GET /api/users/:id 获取单个用户 200 / 404
POST /api/users 创建用户 201
PUT /api/users/:id 更新用户信息 200 / 404
DELETE /api/users/:id 删除用户 204 / 404
完整实现代码
javascript 复制代码
// 【代码注释】文件:api-server.js
// 纯 Node.js RESTful API,无任何第三方依赖
// 运行:node api-server.js

const http = require('http');
const url  = require('url');

// ──────────────────────────────────────
// 内存数据库(重启后清空,仅用于演示)
// 实际项目应替换为 MySQL / MongoDB 等持久化存储
// ──────────────────────────────────────
let users = [
    { id: 1, name: '张三', email: 'zhang@example.com', role: 'admin' },
    { id: 2, name: '李四', email: 'li@example.com',    role: 'user'  },
];
let nextId = 3; // 【代码注释】模拟自增主键

// ──────────────────────────────────────
// 工具函数
// ──────────────────────────────────────

// 【代码注释】统一 JSON 响应封装:设置状态码 + Content-Type + 序列化
function sendJSON(res, status, data) {
    res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
    res.end(JSON.stringify(data, null, 2));
}

// 【代码注释】读取请求体(HTTP body 是流,需分块拼接后解析)
// Express 的 express.json() 中间件做的正是这件事
function parseBody(req) {
    return new Promise((resolve, reject) => {
        let body = '';
        req.on('data', chunk => { body += chunk; });
        req.on('end', () => {
            try {
                resolve(body ? JSON.parse(body) : {});
            } catch {
                reject(new Error('Invalid JSON'));
            }
        });
        req.on('error', reject);
    });
}

// 【代码注释】从路径中提取动态参数 :id
// /api/users/3  →  3
// /api/users    →  null
function extractId(pathname) {
    const match = pathname.match(/^\/api\/users\/(\d+)$/);
    return match ? parseInt(match[1]) : null;
}

// ──────────────────────────────────────
// HTTP 服务器 & 路由分发
// ──────────────────────────────────────
const server = http.createServer(async (req, res) => {
    // 【代码注释】允许所有源跨域访问(生产环境应限制为指定域名)
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');

    // 【代码注释】浏览器发 PUT/DELETE 前会先发 OPTIONS 预检请求
    // 服务器必须回 204 才能放行后续正式请求(CORS 协议)
    if (req.method === 'OPTIONS') {
        res.writeHead(204);
        return res.end();
    }

    const { pathname } = url.parse(req.url);
    const method = req.method;
    const id = extractId(pathname);

    try {
        // ── GET /api/users ──────────────────────────
        if (method === 'GET' && pathname === '/api/users') {
            const { query } = url.parse(req.url, true);
            // 【代码注释】支持 ?role=admin 过滤
            let result = query.role
                ? users.filter(u => u.role === query.role)
                : users;
            return sendJSON(res, 200, { total: result.length, data: result });
        }

        // ── GET /api/users/:id ──────────────────────
        if (method === 'GET' && id !== null) {
            const user = users.find(u => u.id === id);
            if (!user) return sendJSON(res, 404, { error: `用户 ${id} 不存在` });
            return sendJSON(res, 200, user);
        }

        // ── POST /api/users ─────────────────────────
        if (method === 'POST' && pathname === '/api/users') {
            const body = await parseBody(req);
            // 【代码注释】字段校验:name 和 email 必填
            if (!body.name || !body.email) {
                return sendJSON(res, 400, { error: 'name 和 email 为必填字段' });
            }
            // 【代码注释】业务规则:邮箱不允许重复(返回 409 Conflict)
            if (users.find(u => u.email === body.email)) {
                return sendJSON(res, 409, { error: '邮箱已被注册' });
            }
            const newUser = {
                id:    nextId++,
                name:  body.name,
                email: body.email,
                role:  body.role || 'user',
            };
            users.push(newUser);
            // 【代码注释】创建成功返回 201 Created,响应体含新资源
            return sendJSON(res, 201, newUser);
        }

        // ── PUT /api/users/:id ──────────────────────
        if (method === 'PUT' && id !== null) {
            const index = users.findIndex(u => u.id === id);
            if (index === -1) return sendJSON(res, 404, { error: `用户 ${id} 不存在` });
            const body = await parseBody(req);
            // 【代码注释】展开合并,但强制保留原始 id,防止客户端篡改主键
            users[index] = { ...users[index], ...body, id };
            return sendJSON(res, 200, users[index]);
        }

        // ── DELETE /api/users/:id ───────────────────
        if (method === 'DELETE' && id !== null) {
            const index = users.findIndex(u => u.id === id);
            if (index === -1) return sendJSON(res, 404, { error: `用户 ${id} 不存在` });
            users.splice(index, 1);
            // 【代码注释】删除成功返回 204 No Content(无响应体,符合 REST 语义)
            res.writeHead(204);
            return res.end();
        }

        // ── 未匹配路由 ──────────────────────────────
        sendJSON(res, 404, { error: '接口不存在', path: pathname });

    } catch (err) {
        if (err.message === 'Invalid JSON') {
            return sendJSON(res, 400, { error: '请求体 JSON 格式错误' });
        }
        console.error('[Server Error]', err);
        sendJSON(res, 500, { error: '服务器内部错误' });
    }
});

server.listen(3000, () => {
    console.log('RESTful API 服务器启动:http://localhost:3000');
    console.log('\n测试命令(curl):');
    console.log('  # 获取全部用户');
    console.log('  curl http://localhost:3000/api/users');
    console.log('  # 按角色过滤');
    console.log('  curl "http://localhost:3000/api/users?role=admin"');
    console.log('  # 获取单个用户');
    console.log('  curl http://localhost:3000/api/users/1');
    console.log('  # 创建用户');
    console.log('  curl -X POST http://localhost:3000/api/users \\');
    console.log('       -H "Content-Type: application/json" \\');
    console.log('       -d \'{"name":"王五","email":"wang@example.com"}\'');
    console.log('  # 更新用户');
    console.log('  curl -X PUT http://localhost:3000/api/users/1 \\');
    console.log('       -H "Content-Type: application/json" \\');
    console.log('       -d \'{"name":"张三(已更新)"}\'');
    console.log('  # 删除用户');
    console.log('  curl -X DELETE http://localhost:3000/api/users/2');
});

【代码注释】

  • parseBody 手动处理 HTTP 请求流:data 事件分块传入,end 事件表示完整接收------这就是 Express express.json() 中间件的底层逻辑。
  • 状态码语义严格对应 REST 规范:201 = 新资源已创建;204 = 成功无内容;409 = 业务冲突;400 = 客户端参数错误。
  • { ...users[index], ...body, id } 合并更新时把 id 写在最后,防止客户端通过 body 修改主键(安全实践)。
  • 这个服务器与 Express app.get/post/put/delete 的区别仅在于:Express 帮你封装了路由匹配、body 解析、错误处理中间件;底层 HTTP 语义完全一致。

7.6 静态文件服务器(可直接用于前端开发)

将指定目录作为 Web 根目录,对外提供 HTML、CSS、JS、图片等静态资源访问。这是 vite preview / npx serve dist 命令的简化版原理,也是前端工程师最常用的 Node.js 应用场景之一。

javascript 复制代码
// 【代码注释】文件:static-server.js
// 用法:node static-server.js [端口] [目录]
// 示例:node static-server.js 8080 ./dist

const http = require('http');
const fs   = require('fs');
const path = require('path');
const url  = require('url');

// 【代码注释】MIME 类型映射表:文件扩展名 → Content-Type
// 浏览器依赖此字段决定如何解析/渲染文件
const MIME_TYPES = {
    '.html':  'text/html; charset=utf-8',
    '.css':   'text/css; charset=utf-8',
    '.js':    'application/javascript; charset=utf-8',
    '.mjs':   'application/javascript; charset=utf-8',
    '.json':  'application/json; charset=utf-8',
    '.png':   'image/png',
    '.jpg':   'image/jpeg',
    '.jpeg':  'image/jpeg',
    '.gif':   'image/gif',
    '.svg':   'image/svg+xml',
    '.ico':   'image/x-icon',
    '.woff':  'font/woff',
    '.woff2': 'font/woff2',
    '.ttf':   'font/ttf',
    '.mp4':   'video/mp4',
    '.pdf':   'application/pdf',
    '.webp':  'image/webp',
};

const PORT = parseInt(process.argv[2]) || 3000;
// 【代码注释】将根目录解析为绝对路径,防止目录遍历攻击时比较出错
const ROOT = path.resolve(process.argv[3] || '.');

const server = http.createServer((req, res) => {
    // 【代码注释】decodeURIComponent 解码中文路径,如 /图片/logo.png
    const pathname = decodeURIComponent(url.parse(req.url).pathname);

    // ── 安全检查:防止目录遍历攻击 ──────────────────
    // 攻击示例:GET /../../../etc/passwd
    // path.join 会规范化路径,startsWith(ROOT) 确保不逃出根目录
    const filePath = path.join(ROOT, pathname);
    if (!filePath.startsWith(ROOT)) {
        res.writeHead(403, { 'Content-Type': 'text/plain' });
        return res.end('403 Forbidden');
    }

    // 【代码注释】如果请求的是目录,自动寻找 index.html(SPA 常见模式)
    let resolvedPath = filePath;
    if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
        resolvedPath = path.join(filePath, 'index.html');
    }

    // ── 404 处理 ─────────────────────────────────────
    if (!fs.existsSync(resolvedPath)) {
        res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
        return res.end(`
            <!DOCTYPE html>
            <html lang="zh-CN">
            <head><meta charset="utf-8"><title>404</title></head>
            <body>
                <h1>404 - 文件不存在</h1>
                <p>路径:${pathname}</p>
                <p><a href="/">返回首页</a></p>
            </body>
            </html>
        `);
    }

    const ext = path.extname(resolvedPath).toLowerCase();
    // 【代码注释】未知扩展名回退到 octet-stream,浏览器会触发下载而非直接渲染
    const contentType = MIME_TYPES[ext] || 'application/octet-stream';

    // 【代码注释】设置缓存头:开发时可改为 no-cache;生产静态资源打 hash 后可设更长时间
    res.setHeader('Cache-Control', 'public, max-age=3600');
    res.setHeader('Content-Type', contentType);

    // 【代码注释】流式传输:边读边写,内存占用与文件大小无关
    // 对比 fs.readFileSync() 会把整个文件载入内存,大视频/PDF 会 OOM
    const stream = fs.createReadStream(resolvedPath);
    stream.on('error', () => {
        res.writeHead(500);
        res.end('500 Internal Server Error');
    });
    stream.pipe(res);

    // 【代码注释】简单访问日志:时间 + 方法 + 路径 + 类型
    console.log(`[${new Date().toLocaleTimeString()}] ${req.method} ${pathname} → ${contentType}`);
});

server.listen(PORT, () => {
    console.log(`静态文件服务器启动`);
    console.log(`  根目录: ${ROOT}`);
    console.log(`  地址:   http://localhost:${PORT}`);
    console.log(`\n按 Ctrl+C 停止服务器`);
});

经典使用场景:

bash 复制代码
# 【命令注释】为 Vite/Webpack/Rollup 构建产物提供本地预览
node static-server.js 8080 ./dist

# 【命令注释】为课堂练习 HTML 文件提供本地 HTTP 服务
# (直接用 file:// 打开时 fetch/模块化功能受限)
node static-server.js 3000 ./src

# 【命令注释】等效的一行命令(更便捷)
npx serve dist                  # 使用 serve 包
npx http-server dist -p 8080   # 使用 http-server 包

【代码注释】

  • 目录遍历攻击path.join(ROOT, '/../../../etc/passwd')path.join 规范化后为 /etc/passwd,不以 ROOT 开头,被拒绝(403)------这是服务静态文件的必须防御措施。
  • MIME 类型Content-Type: application/javascript 缺失时,现代浏览器会拒绝执行 <script type="module"> 的 JS 文件(MIME 嗅探保护)。
  • SPA 路由 :React Router / Vue Router 的 history 模式需要服务器将所有路径返回 index.html,可在 404 处理中改为 resolvedPath = path.join(ROOT, 'index.html')
  • 流式传输createReadStream().pipe(res) 是 Node.js 背压(backpressure)机制的实际应用,客户端读慢时自动暂停读文件,不会撑爆内存。

8. 核心概念总结与术语解析

核心概念总结

渲染错误: Mermaid 渲染失败: Parse error on line 5: ...nJS require() module.exp ----------------------^ Expecting 'NODE_DESCR', got 'NODE_DEND'

重要术语解析

模块相关术语
  1. CommonJS

    • 定义:Node.js 默认的模块系统规范
    • 特点:同步加载,适合服务端开发
    • 关键字:require(), module.exports, exports
  2. ES Modules (ESM)

    • 定义:ECMAScript 标准的模块系统
    • 特点:异步加载,同时支持浏览器和 Node.js
    • 关键字:import, export, default
  3. 模块路径

    • 定义:在模块系统中引用其他模块的路径
    • 特点:相对于当前文件所在目录,与命令行目录无关
    • 示例:require('./utils/helper')
  4. 文件路径

    • 定义:操作系统中文件的路径
    • 特点:相对于命令行当前目录
    • 示例:fs.readFile('./data.txt')
包管理术语
  1. NPM (Node Package Manager)

    • 定义:Node.js 的包管理工具和在线仓库
    • 功能:包的搜索、安装、发布、管理
    • 官网:https://www.npmjs.com/
  2. package.json

    • 定义:Node.js 项目的配置文件
    • 作用:定义项目元数据、依赖关系、脚本命令
    • 位置:项目根目录
  3. dependencies

    • 定义:项目生产环境依赖的包
    • 安装命令:npm install package-name
    • 使用场景:项目运行时必需的包
  4. devDependencies

    • 定义:项目开发环境依赖的包
    • 安装命令:npm install package-name -D
    • 使用场景:开发工具、测试框架等
  5. 语义化版本 (Semantic Versioning)

    • 格式:MAJOR.MINOR.PATCH (如 1.2.3)
    • 规则:破坏性更新.功能新增.Bug修复
    • 符号:^ 兼容更新, ~ 补丁更新
HTTP 协议术语
  1. HTTP (HyperText Transfer Protocol)

    • 定义:超文本传输协议
    • 作用:定义客户端与服务器之间的通信规则
    • 特点:无状态、基于请求-响应模型
  2. 请求报文

    • 定义:客户端发送给服务器的数据
    • 组成:请求行、请求头、空行、请求体
    • 方法:GET、POST、PUT、DELETE 等
  3. 响应报文

    • 定义:服务器返回给客户端的数据
    • 组成:响应行、响应头、空行、响应体
    • 状态码:200、404、500 等
  4. URL (Uniform Resource Locator)

  5. 状态码

    • 定义:服务器返回的3位数字代码
    • 分类:1xx(信息) 2xx(成功) 3xx(重定向) 4xx(客户端错误) 5xx(服务器错误)
    • 常见:200 OK、404 Not Found、500 Internal Server Error
Node.js HTTP 服务术语
  1. http 模块

    • 定义:Node.js 内置的 HTTP 服务器模块
    • 作用:创建 HTTP 服务器和客户端
    • 导入:const http = require('http');
  2. http.IncomingMessage

    • 定义:HTTP 请求对象
    • 作用:获取客户端请求信息
    • 属性:method、url、headers、httpVersion
  3. http.ServerResponse

    • 定义:HTTP 响应对象
    • 作用:设置服务器响应信息
    • 方法:writeHead()、setHeader()、write()、end()
  4. 端口 (Port)

    • 定义:服务器监听的数字标识
    • 范围:0-65535
    • 常用:80(HTTP)、443(HTTPS)、3000(开发服务器)

最佳实践建议

模块开发
  1. 使用明确的文件扩展名

    javascript 复制代码
    // ✅ 推荐
    const utils = require('./utils.js');
    
    // ❌ 避免(虽然可以省略)
    const utils = require('./utils');

    【代码注释】显式 .js 便于工具链与新人阅读;省略扩展名虽合法,但多扩展名并存时易歧义。

  2. 优先使用 ES Modules

    javascript 复制代码
    // ✅ 推荐(现代 JavaScript)
    import { express } from 'express';
    
    // ❌ 传统(CommonJS)
    const express = require('express');

    【代码注释】新项目优先 ESM;维护老库时可保留 CJS,或通过 "exports" 双格式发布。

  3. 模块路径使用相对路径

    javascript 复制代码
    // ✅ 推荐
    const helper = require('./utils/helper');
    
    // ❌ 避免(绝对路径)
    const helper = require('/absolute/path/to/helper');

    【代码注释】相对路径保证项目可移植;绝对路径换机器即失效。

包管理
  1. 明确区分依赖类型

    bash 复制代码
    # ✅ 生产依赖
    npm install express
    
    # ✅ 开发依赖
    npm install -D jest
    
    # ❌ 避免混淆
    npm install jest --save

    【代码注释】测试/构建工具应进 devDependencies,避免生产镜像体积膨胀。

  2. 使用 package-lock.json

    bash 复制代码
    # ✅ 提交 package-lock.json 到版本控制
    git add package-lock.json
    
    # ❌ 不要忽略 lock 文件
    echo "package-lock.json" >> .gitignore

    【代码注释】锁文件与 npm ci 配合,保证 CI 与本地依赖树一致。

  3. 定期更新依赖

    bash 复制代码
    # ✅ 检查可更新的包
    npm outdated
    
    # ✅ 更新到最新版本
    npm update

    【代码注释】大版本升级前阅读 CHANGELOG,避免破坏性 API 变更。

HTTP 服务开发
  1. 正确处理错误

    javascript 复制代码
    // ✅ 推荐:完整的错误处理
    const server = http.createServer((req, res) => {
        try {
            // 处理请求
            res.end('Success');
        } catch (error) {
            res.statusCode = 500;
            res.end('Internal Server Error');
        }
    });
    
    server.on('error', (err) => {
        if (err.code === 'EADDRINUSE') {
            console.error('端口已被占用');
        }
    });

    【代码注释】createServer 内 try/catch 处理同步逻辑;server.on('error') 捕获监听失败(如端口占用)。

  2. 设置合适的响应头

    javascript 复制代码
    // ✅ 推荐:设置必要的响应头
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Cache-Control', 'no-cache');

    【代码注释】Content-Type 决定客户端如何解析 body;CORS 头仅在与浏览器跨域联调时需要。

  3. 使用流处理大数据

    javascript 复制代码
    // ✅ 推荐:使用流处理文件
    const stream = fs.createReadStream('large-file.txt');
    stream.pipe(res);
    
    // ❌ 避免:一次性读取大文件
    const content = fs.readFileSync('large-file.txt');
    res.end(content);

    【代码注释】pipe 边读边写,内存占用稳定;readFileSync 会把整个文件载入内存,大文件易 OOM。

总结

通过本指南,我们系统地学习了:

  1. Node.js 模块系统:CommonJS 和 ES Modules 的使用方法和区别
  2. NPM 包管理:包的安装、管理、发布等完整流程
  3. HTTP 协议:请求和响应报文的详细结构和工作原理
  4. HTTP 服务开发:使用 Node.js 创建和配置 HTTP 服务器
  5. 实战案例:文件大小转换工具和模块路径的实际应用
  6. 最佳实践:开发规范和常见问题的解决方案

这些知识点为 Node.js 后端开发打下了坚实的基础,掌握了这些内容,你就可以开始构建实际的 Web 应用和服务了。


9. 知识点归纳速查与行业应用场景

9.1 一图总览(复习用)

#mermaid-svg-CqL2mf07aAKihF8U{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-CqL2mf07aAKihF8U .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CqL2mf07aAKihF8U .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CqL2mf07aAKihF8U .error-icon{fill:#552222;}#mermaid-svg-CqL2mf07aAKihF8U .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CqL2mf07aAKihF8U .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CqL2mf07aAKihF8U .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CqL2mf07aAKihF8U .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CqL2mf07aAKihF8U .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CqL2mf07aAKihF8U .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CqL2mf07aAKihF8U .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CqL2mf07aAKihF8U .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CqL2mf07aAKihF8U .marker.cross{stroke:#333333;}#mermaid-svg-CqL2mf07aAKihF8U svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CqL2mf07aAKihF8U p{margin:0;}#mermaid-svg-CqL2mf07aAKihF8U .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CqL2mf07aAKihF8U .cluster-label text{fill:#333;}#mermaid-svg-CqL2mf07aAKihF8U .cluster-label span{color:#333;}#mermaid-svg-CqL2mf07aAKihF8U .cluster-label span p{background-color:transparent;}#mermaid-svg-CqL2mf07aAKihF8U .label text,#mermaid-svg-CqL2mf07aAKihF8U span{fill:#333;color:#333;}#mermaid-svg-CqL2mf07aAKihF8U .node rect,#mermaid-svg-CqL2mf07aAKihF8U .node circle,#mermaid-svg-CqL2mf07aAKihF8U .node ellipse,#mermaid-svg-CqL2mf07aAKihF8U .node polygon,#mermaid-svg-CqL2mf07aAKihF8U .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CqL2mf07aAKihF8U .rough-node .label text,#mermaid-svg-CqL2mf07aAKihF8U .node .label text,#mermaid-svg-CqL2mf07aAKihF8U .image-shape .label,#mermaid-svg-CqL2mf07aAKihF8U .icon-shape .label{text-anchor:middle;}#mermaid-svg-CqL2mf07aAKihF8U .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CqL2mf07aAKihF8U .rough-node .label,#mermaid-svg-CqL2mf07aAKihF8U .node .label,#mermaid-svg-CqL2mf07aAKihF8U .image-shape .label,#mermaid-svg-CqL2mf07aAKihF8U .icon-shape .label{text-align:center;}#mermaid-svg-CqL2mf07aAKihF8U .node.clickable{cursor:pointer;}#mermaid-svg-CqL2mf07aAKihF8U .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CqL2mf07aAKihF8U .arrowheadPath{fill:#333333;}#mermaid-svg-CqL2mf07aAKihF8U .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CqL2mf07aAKihF8U .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CqL2mf07aAKihF8U .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CqL2mf07aAKihF8U .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CqL2mf07aAKihF8U .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CqL2mf07aAKihF8U .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CqL2mf07aAKihF8U .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CqL2mf07aAKihF8U .cluster text{fill:#333;}#mermaid-svg-CqL2mf07aAKihF8U .cluster span{color:#333;}#mermaid-svg-CqL2mf07aAKihF8U 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-CqL2mf07aAKihF8U .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CqL2mf07aAKihF8U rect.text{fill:none;stroke-width:0;}#mermaid-svg-CqL2mf07aAKihF8U .icon-shape,#mermaid-svg-CqL2mf07aAKihF8U .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CqL2mf07aAKihF8U .icon-shape p,#mermaid-svg-CqL2mf07aAKihF8U .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CqL2mf07aAKihF8U .icon-shape .label rect,#mermaid-svg-CqL2mf07aAKihF8U .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CqL2mf07aAKihF8U .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CqL2mf07aAKihF8U .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CqL2mf07aAKihF8U :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} require/import
本地模块
node_modules
npm install
package.json + lock
团队协作 / CI
http 服务
浏览器 fetch

9.2 模块系统对照表

维度 CommonJS ES Modules
关键字 require / module.exports import / export
加载 同步(适合服务端) 静态分析、可 tree-shake
Node 默认 .js 且无 "type":"module" .mjs"type":"module"
动态导入 require() 任意位置 import() 返回 Promise
经典场景 Express 传统项目、工具脚本 前端构建、新一代全栈框架

9.3 NPM 命令速查

目的 命令 备注
装生产依赖 npm i express 写入 dependencies
装开发依赖 npm i -D jest 写入 devDependencies
按锁文件装 npm ci CI 环境推荐,更快更严
查过期包 npm outdated 配合升级策略
跑脚本 npm run dev start 可省略 run
一次性 CLI npx create-vite@latest 无需全局安装
发布 npm publish 需官方 registry + 登录

9.4 HTTP 与 Node 服务速查

概念 记忆要点 Node API
请求行 方法 + URL + 版本 req.method, req.url
请求头 键值对元数据 req.headers
请求体 POST/PUT 常有 req.on('data')
响应行 版本 + 状态码 + 描述 res.statusCode
响应头 Content-Type 等 res.setHeader / writeHead
响应体 HTML/JSON/流 res.end() / pipe

9.5 行业应用场景(技术向)

场景 用到的本章知识 说明
前端工程初始化 npxpackage.json scripts 一条命令生成 Vite/React 模板
接口联调 HTTP 报文、http 或 Express 理解 401/403/404 与 CORS 头
私有 NPM 源 registry 配置、cnpm/nrm 内网包与公网包隔离
发布内部 CLI bin + shebang 统一团队脚手架、代码生成器
微服务健康检查 GET + 200 + JSON {"status":"ok"} 供网关探测
静态资源服务 文件路径 + createReadStream 大文件用流,避免内存暴涨

9.6 常见坑与对策

现象 原因 对策
Cannot find module 路径或 node_modules 缺失 检查相对路径;根目录 npm install
require is not defined 在 ESM 里用了 CJS 改用 import.cjs
读不到 data.txt 混淆文件路径与模块路径 文件操作用 path.join(__dirname, ...)
端口 EADDRINUSE 3000 已被占用 换端口或结束占用进程
npm publish 失败 用了镜像源 npm config set registry https://registry.npmjs.org/
浏览器 fetch 失败 未开服务或未配 CORS 启动 server 并设置 Access-Control-Allow-Origin

9.7 学习路径建议

  1. 手写 CommonJS 小模块(工具函数 + require)。
  2. npm init -y 建工程,区分 dependencies / devDependencies
  3. 读一个真实项目的 package.json scripts ,本地 npm run 跑通。
  4. 用原生 http 写一个返回 JSON 的接口,再用 HTML fetch 调用。
  5. 尝试 npm link + bin 发布本地 CLI,理解包与命令的关系。

【代码注释】

  • 建议巩固顺序:① require / module.exportspackage.json + npm install ③ 原生 http.createServernpm publishnpm link CLI。
  • 已掌握「请求/响应、状态码、CORS、模块路径」后,学习 Express 只是在同一模型上加路由表与中间件链,不会重新学 HTTP。
  • MODULE_NOT_FOUND:查相对路径、node_modules 是否安装、是否拼错包名;遇 EADDRINUSE:换端口或关掉占用 3000 的进程。
  • 与 Day07 的 fs/path、Day09~11 的 Express 形成连续链路,本文是包管理与 HTTP 的枢纽章节。

9.8 安全与质量工具

npm audit --- 依赖安全漏洞扫描

npm audit 将项目依赖树与 npm 安全公告数据库(npm Advisory Database)比对,输出已知漏洞报告。这是生产项目必须集成的安全卡口。

bash 复制代码
# 【命令注释】扫描全部依赖漏洞
npm audit

# 【命令注释】典型输出示例:
# ┌─────────────┬──────────────────────────────────────────┐
# │ moderate    │ ReDoS 漏洞(正则表达式拒绝服务)           │
# ├─────────────┼──────────────────────────────────────────┤
# │ Package     │ some-package@1.2.3                       │
# │ Patched in  │ >=1.2.4                                  │
# │ Dependency  │ your-project > some-package              │
# └─────────────┴──────────────────────────────────────────┘
# 1 moderate severity vulnerability found

# 【命令注释】自动修复(只升级不引入破坏性变更的包)
npm audit fix

# 【命令注释】强制修复(可能涉及 MAJOR 版本升级,需人工验证)
npm audit fix --force

# 【命令注释】只报告高危及以上漏洞(CI 中常用作合并门禁)
npm audit --audit-level=high

# 【命令注释】仅扫描生产依赖(忽略 devDependencies)
npm audit --omit=dev

# 【命令注释】输出为 JSON(便于 CI 系统解析)
npm audit --json

漏洞级别说明:

级别 中文 建议操作
critical 严重 立即修复,考虑临时下线
high 高危 48 小时内升级依赖
moderate 中危 纳入下次迭代修复
low 低危 择机修复
info 信息 关注即可

在 CI/CD 中集成:

json 复制代码
{
  "scripts": {
    "pretest": "npm audit --audit-level=high",
    "test": "jest"
  }
}

【代码注释】pretest 钩子在 npm test 前自动执行。--audit-level=high 发现高危漏洞时 npm audit 返回非 0 exit code,CI pipeline 自动中断,强制在合并前修复。


.npmrc 配置文件详解

.npmrc 是 npm 的持久化配置文件,支持三级作用域(优先级从高到低):

复制代码
项目级:<项目根目录>/.npmrc         # 只影响本项目
用户级:~/.npmrc                    # 影响当前系统用户
全局级:$(npm prefix -g)/.npmrc     # 影响所有用户(需管理员)
ini 复制代码
# 【代码注释】.npmrc 完整配置示例

# ── 镜像源 ──────────────────────────────────────────────
# 项目级覆盖,不影响全局 npm 配置
registry=https://registry.npmmirror.com/

# 【代码注释】作用域包单独指向私有源
# @mycompany 开头的包走内网源,其他包走淘宝镜像
@mycompany:registry=https://npm.mycompany.internal/

# ── 认证 ─────────────────────────────────────────────────
# 【代码注释】私有源 Token(CI 环境通过环境变量注入,不硬编码)
//npm.mycompany.internal/:_authToken=${NPM_TOKEN}

# ── 安装行为 ─────────────────────────────────────────────
# 【代码注释】save-exact=true:npm install 时自动锁定精确版本(不加 ^)
save-exact=true

# 【代码注释】engine-strict=true:node/npm 版本不满足 engines 字段时报错
engine-strict=true

常用场景:

bash 复制代码
# 【命令注释】临时切换源(不修改配置文件)
npm install express --registry=https://registry.npmmirror.com

# 【命令注释】查看当前所有生效配置(含来源文件)
npm config list

# 【命令注释】查看某项配置的值
npm config get registry

【代码注释】

  • ${NPM_TOKEN} 语法让 CI 从环境变量读取 Token,避免将密钥提交到 Git------这是私有包认证的最佳实践。
  • 项目根 .npmrc 提交到 Git 可统一团队镜像源,无需每人手动配置;但 Token 行绝不能提交
  • save-exact=true 配合 package-lock.json 双保险,彻底锁定版本,适合对稳定性要求极高的项目。

package.json 高级字段

peerDependencies(对等依赖)------ 插件库必备:

json 复制代码
{
  "name": "my-react-plugin",
  "version": "1.0.0",
  "peerDependencies": {
    "react": ">=17.0.0",
    "react-dom": ">=17.0.0"
  },
  "peerDependenciesMeta": {
    "react-dom": { "optional": true }
  }
}

【代码注释】peerDependencies 声明「我需要 React,但不自己安装,由使用我的应用提供」。插件类库(eslint-plugin-*babel-plugin-*、React 组件库)必须 使用此字段,否则同一项目中可能出现多个 React 实例,导致 Hook 内部状态错乱(经典 Bug:Hooks can only be called inside of the body of a function component)。

exports 字段(现代包的条件导出):

json 复制代码
{
  "name": "my-lib",
  "version": "1.0.0",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "exports": {
    ".": {
      "import":  "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types":   "./dist/index.d.ts"
    },
    "./utils": {
      "import":  "./dist/utils.mjs",
      "require": "./dist/utils.cjs"
    }
  }
}

【代码注释】

  • exports 是 Node.js 12+ 支持的现代字段,优先级高于 main
  • import 条件走 ESM(Vite / 现代打包器使用),require 条件走 CJS(Node.js 原生使用),实现双格式发布
  • 未在 exports 中列出的子路径会返回 ERR_PACKAGE_PATH_NOT_EXPORTED,增强封装性(防止用户直接访问内部模块)。
  • types 条件让 TypeScript 自动找到类型声明文件,无需额外配置 typings 字段。

9.9 常见面试题解析

Q1:CommonJS 和 ES Modules 的核心区别?
维度 CommonJS ES Modules
关键字 require() / module.exports import / export
加载时机 运行时(动态、按需) 编译时(静态分析)
值的语义 导出值的副本 导出值的实时绑定
Tree-shaking 困难 支持(打包器可静态分析未用代码)
动态导入 require() 任意位置均可 import() 返回 Promise
Node 默认 .js(无 "type":"module" .mjs"type":"module"
javascript 复制代码
// 【代码注释】ESM 实时绑定 vs CJS 值拷贝(经典面试题)

// ── ESM:导出的是变量的"引用",修改可见 ──
// counter.mjs
export let count = 0;
export const increment = () => count++;

// main.mjs
import { count, increment } from './counter.mjs';
increment();
console.log(count); // 1  ← 实时绑定,能看到变化

// ── CJS:require() 得到的是导出时的值快照 ──
// counter.js
let count = 0;
module.exports = { count, increment: () => count++ };

// main.js
const { count, increment } = require('./counter.js');
increment();
console.log(count); // 0  ← 值拷贝,看不到变化!
Q2:require() 有缓存机制吗?如何清除?

有。Node.js 第一次 require 某个模块时,执行该模块并将 module.exports 缓存require.cache 对象中;后续再 require 同路径时直接返回缓存,不重新执行模块代码。

javascript 复制代码
// 【代码注释】require 缓存演示
// counter.js:每次 require 不会重置 count
let count = 0;
module.exports = { get: () => count, inc: () => count++ };

// app.js
const a = require('./counter');
const b = require('./counter'); // 同一对象引用(缓存命中)

a.inc();
console.log(b.get()); // 1 ← a 和 b 指向同一个模块导出对象

// 【代码注释】手动清除缓存(极少使用,常见于热更新实现或测试隔离)
delete require.cache[require.resolve('./counter')];
const c = require('./counter'); // 重新执行,count 归零
console.log(c.get()); // 0

【代码注释】:缓存机制是 Node.js 实现单例模式的天然基础------数据库连接、配置对象等全局共享资源可直接利用此特性。

Q3:npm installnpm ci 有什么区别?
特性 npm install npm ci
使用场景 日常开发 CI/CD 流水线、Docker 构建
lock 文件 可以创建/更新 必须存在且与 package.json 一致
node_modules 增量安装(不删除已有) 先完全删除再重新安装
安装速度 稍慢 更快(跳过版本范围解析)
一致性保证 较弱 强(100% 按 lock 文件安装)
lock 不一致时 更新 lock 文件 直接报错退出
bash 复制代码
# 【命令注释】CI 环境标准实践
npm ci                      # 严格按 lock 安装(保证可复现)
npm ci --omit=dev           # 跳过 devDependencies(生产镜像)
npm test                    # 运行测试套件

# 【命令注释】Docker 中的最佳实践
# COPY package.json package-lock.json ./
# RUN npm ci --omit=dev     # 每次构建依赖树一致
Q4:什么是语义化版本?^~ 的区别?
复制代码
版本格式:MAJOR.MINOR.PATCH
  MAJOR:不兼容的 API 变更     1.x.x → 2.0.0
  MINOR:向下兼容的新功能新增  1.0.x → 1.1.0
  PATCH:向下兼容的 Bug 修复   1.0.0 → 1.0.1
json 复制代码
{
  "express": "^4.18.2",  // >=4.18.2 <5.0.0  允许 MINOR + PATCH 更新
  "lodash":  "~4.17.21", // >=4.17.21 <4.18.0 只允许 PATCH 更新
  "react":   "18.2.0"    // 精确版本,不允许任何自动更新
}

【代码注释】package-lock.json 会将范围锁定到首次安装时的精确版本;npm ci 使用 lock 中的精确版本,因此实际安装结果不受 ^/~ 影响------两者配合才是完整的版本管理方案。

Q5:HTTP 304 状态码的工作原理?

304 Not Modified 是浏览器协商缓存的核心机制,服务器返回 304 时响应体为空,浏览器直接使用本地缓存,节省带宽。
服务器 浏览器 服务器 浏览器 #mermaid-svg-mPL4PNQT6IqpMR1Y{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-mPL4PNQT6IqpMR1Y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mPL4PNQT6IqpMR1Y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mPL4PNQT6IqpMR1Y .error-icon{fill:#552222;}#mermaid-svg-mPL4PNQT6IqpMR1Y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mPL4PNQT6IqpMR1Y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mPL4PNQT6IqpMR1Y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mPL4PNQT6IqpMR1Y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mPL4PNQT6IqpMR1Y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mPL4PNQT6IqpMR1Y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mPL4PNQT6IqpMR1Y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mPL4PNQT6IqpMR1Y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mPL4PNQT6IqpMR1Y .marker.cross{stroke:#333333;}#mermaid-svg-mPL4PNQT6IqpMR1Y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mPL4PNQT6IqpMR1Y p{margin:0;}#mermaid-svg-mPL4PNQT6IqpMR1Y .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mPL4PNQT6IqpMR1Y text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-mPL4PNQT6IqpMR1Y .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-mPL4PNQT6IqpMR1Y .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-mPL4PNQT6IqpMR1Y .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-mPL4PNQT6IqpMR1Y .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-mPL4PNQT6IqpMR1Y #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-mPL4PNQT6IqpMR1Y .sequenceNumber{fill:white;}#mermaid-svg-mPL4PNQT6IqpMR1Y #sequencenumber{fill:#333;}#mermaid-svg-mPL4PNQT6IqpMR1Y #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-mPL4PNQT6IqpMR1Y .messageText{fill:#333;stroke:none;}#mermaid-svg-mPL4PNQT6IqpMR1Y .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mPL4PNQT6IqpMR1Y .labelText,#mermaid-svg-mPL4PNQT6IqpMR1Y .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-mPL4PNQT6IqpMR1Y .loopText,#mermaid-svg-mPL4PNQT6IqpMR1Y .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-mPL4PNQT6IqpMR1Y .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-mPL4PNQT6IqpMR1Y .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-mPL4PNQT6IqpMR1Y .noteText,#mermaid-svg-mPL4PNQT6IqpMR1Y .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-mPL4PNQT6IqpMR1Y .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mPL4PNQT6IqpMR1Y .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mPL4PNQT6IqpMR1Y .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mPL4PNQT6IqpMR1Y .actorPopupMenu{position:absolute;}#mermaid-svg-mPL4PNQT6IqpMR1Y .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-mPL4PNQT6IqpMR1Y .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mPL4PNQT6IqpMR1Y .actor-man circle,#mermaid-svg-mPL4PNQT6IqpMR1Y line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-mPL4PNQT6IqpMR1Y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 缓存文件和 ETag 值 使用本地缓存渲染页面 GET /style.css(首次请求) 200 OK + ETag: "abc123" + 文件内容(完整响应) GET /style.css + If-None-Match: "abc123"(再次请求) 304 Not Modified(无响应体,节省带宽)

javascript 复制代码
// 【代码注释】Node.js 实现 ETag 协商缓存
const http   = require('http');
const fs     = require('fs');
const crypto = require('crypto');

http.createServer((req, res) => {
    const content = fs.readFileSync('./style.css');
    // 【代码注释】对文件内容求 MD5 哈希,内容变化则 ETag 变化
    const etag = crypto.createHash('md5').update(content).digest('hex');

    // 【代码注释】对比客户端缓存的 ETag 版本
    if (req.headers['if-none-match'] === etag) {
        res.writeHead(304); // 文件未变化,让浏览器用缓存
        return res.end();
    }

    res.setHeader('ETag', etag);
    res.setHeader('Content-Type', 'text/css');
    res.end(content); // 200 + 完整内容
}).listen(3000);
Q6:CORS 跨域的本质是什么?如何在 Node.js 中处理?

跨域的本质 :浏览器的同源策略 (Same-Origin Policy)限制了从 A 域向 B 域发起的 fetch/XMLHttpRequest 请求。CORS(Cross-Origin Resource Sharing)是服务器通过响应头告知浏览器「允许哪些源访问」的机制。

javascript 复制代码
// 【代码注释】Node.js 完整 CORS 处理
const http = require('http');

http.createServer((req, res) => {
    // 【代码注释】允许指定域名(生产环境不要用 *,尤其是带凭证的请求)
    res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com');

    // 【代码注释】OPTIONS 预检请求:浏览器在发 PUT/DELETE/自定义 Header 前自动发出
    // 服务器必须回 204 并带上允许信息,浏览器才会发出正式请求
    if (req.method === 'OPTIONS') {
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
        res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
        res.setHeader('Access-Control-Max-Age', '86400'); // 预检结果缓存 24 小时
        res.writeHead(204);
        return res.end();
    }

    res.end(JSON.stringify({ data: 'Hello CORS!' }));
}).listen(3000);

CORS 头对照:

请求方向 请求头 响应头 作用
浏览器 → 服务器 Origin Access-Control-Allow-Origin 来源与允许来源匹配
预检 → 服务器 Access-Control-Request-Method Access-Control-Allow-Methods 允许的 HTTP 方法
预检 → 服务器 Access-Control-Request-Headers Access-Control-Allow-Headers 允许的请求头
- - Access-Control-Max-Age 预检结果缓存时间(秒)
- - Access-Control-Allow-Credentials 是否允许携带 Cookie

【代码注释】 :CORS 是浏览器行为------Postman / curl 不受同源策略限制,服务器端 Node.js 代码调接口也不受限;只有浏览器中的前端代码才需要 CORS 头配合。

Q7:module.exportsexports 的区别?
javascript 复制代码
// 【代码注释】Node.js 源码中,每个模块初始化时执行了:
// const exports = module.exports = {};
// 因此初始状态下两者指向同一对象

// ✅ 正确:通过 exports 追加属性(两者仍指向同一对象)
exports.add = (a, b) => a + b;
exports.sub = (a, b) => a - b;
// require('./math') → { add: fn, sub: fn }

// ✅ 正确:替换整个 module.exports(exports 变量被遗弃,无副作用)
module.exports = function add(a, b) { return a + b; };
// require('./add') → function add

// ❌ 错误:直接替换 exports 变量(断开了与 module.exports 的引用)
exports = { add: (a, b) => a + b };
// require('./broken') → {}  ← 拿到空对象!因为 module.exports 没变

// 【代码注释】记忆口诀:
// exports 是 module.exports 的快捷方式,只能"追加"不能"替换"
// 最终 require() 返回的永远是 module.exports 的值
Q8:Node.js 单线程如何处理高并发?

Node.js 使用事件循环(Event Loop)+ 非阻塞 I/O 处理高并发,而不是多线程。

复制代码
┌───────────────────────────────────────────────────────────────┐
│                        Event Loop                             │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐        │
│  │  timers  │→│  poll    │→│  check   │→│  close   │→ ...   │
│  │setTimeout│ │I/O事件   │ │setImmed  │ │callbacks │        │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘        │
└───────────────────────────────────────────────────────────────┘
        ↕ (非阻塞)
┌───────────────────────────────────────────────────────────────┐
│              libuv 线程池(默认 4 线程)                        │
│   文件 I/O  │  DNS 解析  │  加密运算  │  压缩解压              │
└───────────────────────────────────────────────────────────────┘
javascript 复制代码
// 【代码注释】1000 个并发请求的处理方式

// ❌ 阻塞模式:每个请求都等待磁盘读取,后面的请求无法响应
const data = fs.readFileSync('./big-file.txt'); // 阻塞事件循环!

// ✅ 非阻塞模式:注册回调后立即返回,事件循环继续处理其他请求
// 磁盘读取完成后,libuv 通知事件循环,回调入队执行
fs.readFile('./big-file.txt', (err, data) => {
    res.end(data); // 读完再响应
});
// 这行代码注册回调后立即继续 ↓
// 事件循环继续处理下一个请求(高并发的秘诀)

【代码注释】 :Node.js 的并发模型与 Java/PHP 的「一请求一线程」完全不同。JavaScript 业务代码是单线程 执行的(避免锁和竞争条件),但 I/O 操作委托给 libuv 线程池异步处理。因此,CPU 密集型任务 (大量计算)会阻塞事件循环,应使用 worker_threads 或子进程处理。

相关推荐
小二·1 小时前
Vue 3 组合式 API 进阶实战
前端·javascript·vue.js
12点一刻1 小时前
npx 使用入门教程:是什么、怎么用、和 npm 有什么区别
前端·npm·node.js
console.log('npc')1 小时前
将 Figma 接入 Codex MCP:从 `/plugins` 到本地插件配置的完整教程
前端·人工智能·python·figma·code·codex·mcp
大家的林语冰2 小时前
React 生态大迁徙,脸书源码仓库跑路,核心技术栈全员加盟 React 基金会!
前端·javascript·react.js
Sca_杰2 小时前
速通抖音开放平台API-生活服务商应用
javascript·node.js
AI智图坊2 小时前
亚马逊多站点Listing视觉制作的效率瓶颈与AI解决方案:GPT-Image-2与Nano Banana Pro双模型分析
大数据·前端·数据库·人工智能·自动化·aigc
console.log('npc')2 小时前
核心实战篇 生成式 UI+A2UI 协议 + 全栈 Agent 项目落地
node.js·react·#生成式ui·a2ui协议·ui agent·ai前端实战
Rain5092 小时前
1.3. Next.js与Nest.js在AI数据分析中的角色
前端·javascript·人工智能·后端·数据分析·node.js·ai编程
wanghao6664552 小时前
精益方法论:用更少的资源创造更大的价值
大数据·前端·数据库·敏捷开发