55.Node.js 技术指南

目录

点击展开目录

Node.js 概述

什么是Node.js

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它使 JavaScript 能够脱离浏览器在服务器端运行。Node.js 采用事件驱动、非阻塞 I/O 模型,使其轻量且高效,非常适合构建数据密集型的实时应用程序。

核心定义要点:

特性说明
运行时环境不是编程语言,而是让 JavaScript 运行在服务端的平台
V8 引擎使用 Google 开发的高性能 JavaScript 引擎
事件驱动基于事件循环机制处理并发请求
非阻塞 I/O异步处理 I/O 操作,不会阻塞主线程
单线程主线程单线程执行,通过事件循环实现高并发

Node.js 的本质理解:

graph LR subgraph "传统服务端" A["JavaScript"] --> B["浏览器环境"] end subgraph "Node.js" C["JavaScript"] --> D["V8引擎"] D --> E["Node.js运行时"] E --> F["操作系统API"] end style A fill:#f9f,stroke:#333 style C fill:#9f9,stroke:#333 style D fill:#ff9,stroke:#333 style E fill:#9ff,stroke:#333

Node.js 的出现打破了 JavaScript 只能在浏览器中运行的限制,使得前后端可以使用同一种语言开发,大大提高了开发效率和代码复用性。

Node.js发展历史

重要里程碑:

时间事件意义
2009年Ryan Dahl 发布 Node.js开创服务端 JavaScript 时代
2010年npm 包管理器发布建立生态系统基础
2011年Windows 版本发布跨平台支持
2014年io.js 分支出现社区分裂与技术演进
2015年Node.js 基金会成立io.js 合并,统一发展
2015年Node.js 4.0 发布支持 ES6 特性
2017年Node.js 8.0 发布支持 async/await
2019年Node.js 12.0 发布支持 ES Modules
2021年Node.js 16.0 发布V8 9.0,性能大幅提升
2023年Node.js 20.0 发布稳定的测试运行器、权限模型
2024年Node.js 22.0 发布原生 WebSocket 客户端支持

版本发布策略:

Node.js 采用偶数版本为 LTS(长期支持)版本的策略:

  • Current 版本:最新特性,适合尝鲜
  • Active LTS:积极维护,推荐生产使用
  • Maintenance LTS:仅修复关键 bug 和安全问题

Node.js特点与优势

核心特点:

graph TB subgraph "Node.js 核心特点" A["事件驱动"] --> E["高并发处理能力"] B["非阻塞I/O"] --> E C["单线程模型"] --> F["低内存占用"] D["V8引擎"] --> G["高执行效率"] end subgraph "带来的优势" E --> H["适合I/O密集型应用"] F --> I["易于水平扩展"] G --> J["快速响应"] end style A fill:#e1f5fe,stroke:#333 style B fill:#e1f5fe,stroke:#333 style C fill:#e1f5fe,stroke:#333 style D fill:#e1f5fe,stroke:#333 style E fill:#c8e6c9,stroke:#333 style F fill:#c8e6c9,stroke:#333 style G fill:#c8e6c9,stroke:#333

1. 事件驱动与非阻塞 I/O

Node.js 的核心优势在于其事件驱动的非阻塞 I/O 模型。当执行 I/O 操作(如读取文件、数据库查询、网络请求)时,Node.js 不会等待操作完成,而是继续执行后续代码,当 I/O 操作完成后通过回调函数处理结果。

阻塞 vs 非阻塞对比:

模型处理方式资源利用适用场景
阻塞 I/O等待操作完成才继续线程空闲等待,资源浪费简单同步逻辑
非阻塞 I/O发起操作后立即返回充分利用 CPU 时间高并发 I/O 密集型

2. 单线程优势

  • 无需处理多线程同步问题:避免死锁、竞态条件
  • 内存占用低:每个连接不需要新建线程
  • 上下文切换开销小:没有线程切换成本

3. 跨平台支持

Node.js 通过 libuv 库实现跨平台,支持:

  • Windows
  • macOS
  • Linux
  • 各种 Unix 系统

4. 丰富的生态系统

npm 是世界上最大的开源库生态系统,拥有超过 200 万个包,几乎涵盖所有开发需求。

Node.js应用场景

最适合的场景:

场景类型具体应用原因
实时应用聊天室、在线游戏、协作工具WebSocket 支持好,事件驱动
API 服务RESTful API、GraphQL高并发处理能力
微服务服务拆分、API 网关轻量、启动快
流处理视频流、文件上传下载Stream API 强大
服务端渲染SSR、同构应用前后端统一技术栈
工具开发CLI 工具、构建工具npm 生态丰富
IoT 应用物联网网关、数据采集轻量、事件驱动

不太适合的场景:

场景原因替代方案
CPU 密集型计算单线程会阻塞事件循环Go、Rust、C++
复杂事务处理缺乏成熟的事务框架Java Spring
大型单体应用类型系统较弱TypeScript + NestJS

典型应用架构:

graph TB subgraph "前端层" A["Web浏览器"] B["移动App"] end subgraph "Node.js服务层" C["API Gateway
Node.js"] D["业务服务1
Node.js"] E["业务服务2
Node.js"] F["实时服务
WebSocket"] end subgraph "数据层" G["MySQL"] H["MongoDB"] I["Redis"] end A --> C B --> C C --> D C --> E C --> F D --> G D --> I E --> H E --> I F --> I style C fill:#4fc3f7,stroke:#333 style D fill:#81c784,stroke:#333 style E fill:#81c784,stroke:#333 style F fill:#ffb74d,stroke:#333

Node.js与其他技术对比

Node.js vs 其他后端技术:

对比维度Node.jsJavaPythonGo
并发模型事件驱动单线程多线程多线程/协程协程(goroutine)
性能I/O密集型优秀全面均衡相对较慢高性能
学习曲线低(前端转型容易)较高中等
生态系统npm 最丰富Maven 成熟pip 丰富相对较少
类型系统弱类型(可用TS)强类型弱类型强类型
内存占用较低较高中等
启动速度中等
适用场景API、实时应用企业级应用AI/ML、脚本云原生、高性能

选型建议:

  • 选择 Node.js:I/O 密集型、实时应用、前后端统一技术栈、快速迭代
  • 选择 Java:大型企业应用、复杂业务逻辑、强类型需求
  • 选择 Go:高性能要求、云原生应用、系统编程
  • 选择 Python:AI/ML、数据分析、快速原型

JavaScript语法基础

Node.js 使用 JavaScript 作为编程语言,掌握 JavaScript 语法是学习 Node.js 的基础。本节介绍 JavaScript 核心语法和 ES6+ 新特性。

变量与数据类型

变量声明方式:

关键字作用域可重新赋值可重复声明提升行为
var函数作用域变量提升(值为 undefined)
let块级作用域暂时性死区(TDZ)
const块级作用域暂时性死区(TDZ)

使用建议:

  • 优先使用 const:不需要重新赋值的变量
  • 需要重新赋值时用 let:循环变量、累加器等
  • 避免使用 var:容易造成变量污染

数据类型:

JavaScript 有 8 种数据类型,分为原始类型引用类型

类型分类示例typeof 结果
undefined原始类型undefined“undefined”
null原始类型null“object”(历史遗留)
boolean原始类型true, false“boolean”
number原始类型42, 3.14, NaN, Infinity“number”
bigint原始类型9007199254740991n“bigint”
string原始类型'hello', "world"“string”
symbol原始类型Symbol('id')“symbol”
object引用类型{}, [], function(){}“object”/“function”

类型判断方法:

// typeof - 适合判断原始类型
typeof 'hello'      // 'string'
typeof 42           // 'number'
typeof true         // 'boolean'
typeof undefined    // 'undefined'
typeof Symbol()     // 'symbol'
typeof 10n          // 'bigint'

// instanceof - 判断对象类型
[] instanceof Array           // true
{} instanceof Object          // true
new Date() instanceof Date    // true

// Object.prototype.toString - 最准确的方法
Object.prototype.toString.call([])        // '[object Array]'
Object.prototype.toString.call(null)      // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'

// Array.isArray - 判断数组
Array.isArray([])    // true
Array.isArray({})    // false

类型转换:

转换目标方法示例
转字符串String(), .toString(), + ''String(123)'123'
转数字Number(), parseInt(), parseFloat(), +Number('42')42
转布尔Boolean(), !!Boolean('')false

假值(Falsy)列表:

// 以下值转为布尔值都是 false
false, 0, -0, 0n, '', null, undefined, NaN

函数与作用域

函数定义方式:

方式语法特点
函数声明function fn() {}会提升,有 this
函数表达式const fn = function() {}不提升,有 this
箭头函数const fn = () => {}不提升,无自己的 this
方法简写{ fn() {} }对象方法,有 this

箭头函数特点:

特点说明
没有自己的 this继承外层作用域的 this
没有 arguments使用剩余参数 ...args
不能作为构造函数不能使用 new
没有 prototype不能定义原型方法
简洁语法单表达式可省略 return
// 箭头函数简写
const add = (a, b) => a + b;           // 单表达式,省略 return
const square = x => x * x;              // 单参数,省略括号
const getObj = () => ({ name: 'test' }); // 返回对象需要括号

作用域类型:

graph TB subgraph "作用域层级" A["全局作用域
Global Scope"] B["函数作用域
Function Scope"] C["块级作用域
Block Scope"] end A --> B B --> C style A fill:#f44336,stroke:#333,color:#fff style B fill:#ff9800,stroke:#333 style C fill:#4caf50,stroke:#333,color:#fff

闭包(Closure):

闭包是指函数能够访问其词法作用域外的变量,即使函数在其词法作用域外执行。

function createCounter() {
    let count = 0;  // 私有变量
    return {
        increment: () => ++count,
        decrement: () => --count,
        getCount: () => count
    };
}

const counter = createCounter();
counter.increment();  // 1
counter.increment();  // 2
counter.getCount();   // 2

闭包应用场景:

场景说明
数据私有化模拟私有变量
函数工厂创建定制函数
回调函数保持上下文
模块模式封装模块

this 指向规则:

调用方式this 指向示例
普通函数调用全局对象(严格模式为 undefined)fn()
方法调用调用对象obj.fn()
构造函数调用新创建的实例new Fn()
call/apply/bind指定的对象fn.call(obj)
箭头函数外层作用域的 this() => {}

对象与数组

对象操作:

// 对象创建
const obj = { name: 'Node.js', version: 20 };

// 属性访问
obj.name          // 点语法
obj['name']       // 方括号语法(支持动态属性名)

// 属性操作
obj.author = 'Ryan Dahl';  // 添加属性
delete obj.version;         // 删除属性
'name' in obj;              // 检查属性存在

// 对象方法
Object.keys(obj)            // 获取所有键
Object.values(obj)          // 获取所有值
Object.entries(obj)         // 获取键值对数组
Object.assign({}, obj)      // 浅拷贝
Object.freeze(obj)          // 冻结对象
Object.seal(obj)            // 密封对象

对象解构:

const user = { name: 'John', age: 25, city: 'NYC' };

// 基本解构
const { name, age } = user;

// 重命名
const { name: userName } = user;

// 默认值
const { country = 'USA' } = user;

// 嵌套解构
const { address: { street } } = { address: { street: '123 Main' } };

// 剩余属性
const { name, ...rest } = user;  // rest = { age: 25, city: 'NYC' }

数组操作:

方法说明是否修改原数组
push/pop末尾添加/删除
unshift/shift开头添加/删除
splice任意位置增删改
slice截取子数组
concat合并数组
map映射转换
filter过滤
reduce归约
find/findIndex查找元素/索引
some/every部分/全部满足
includes是否包含
flat/flatMap扁平化
sort排序
reverse反转

数组高阶函数:

const numbers = [1, 2, 3, 4, 5];

// map - 映射
const doubled = numbers.map(n => n * 2);  // [2, 4, 6, 8, 10]

// filter - 过滤
const evens = numbers.filter(n => n % 2 === 0);  // [2, 4]

// reduce - 归约
const sum = numbers.reduce((acc, n) => acc + n, 0);  // 15

// find - 查找
const found = numbers.find(n => n > 3);  // 4

// some/every - 条件判断
numbers.some(n => n > 4);   // true
numbers.every(n => n > 0);  // true

// 链式调用
const result = numbers
    .filter(n => n % 2 === 0)
    .map(n => n * 2)
    .reduce((acc, n) => acc + n, 0);  // 12

数组解构:

const arr = [1, 2, 3, 4, 5];

// 基本解构
const [first, second] = arr;  // 1, 2

// 跳过元素
const [, , third] = arr;  // 3

// 剩余元素
const [head, ...tail] = arr;  // 1, [2, 3, 4, 5]

// 默认值
const [a, b, c = 0] = [1, 2];  // 1, 2, 0

// 交换变量
let x = 1, y = 2;
[x, y] = [y, x];  // x = 2, y = 1

ES6+新特性

模板字符串:

const name = 'Node.js';
const version = 20;

// 字符串插值
const msg = `Welcome to ${name} v${version}`;

// 多行字符串
const html = `
    <div>
        <h1>${name}</h1>
        <p>Version: ${version}</p>
    </div>
`;

// 标签模板
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => 
        result + str + (values[i] ? `<mark>${values[i]}</mark>` : ''), '');
}
const highlighted = highlight`Hello ${name}!`;

展开运算符(Spread):

// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];  // [1, 2, 3, 4, 5]

// 对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };  // { a: 1, b: 2, c: 3 }

// 函数参数
const nums = [1, 2, 3];
Math.max(...nums);  // 3

// 复制数组/对象(浅拷贝)
const arrCopy = [...arr1];
const objCopy = { ...obj1 };

剩余参数(Rest):

// 函数剩余参数
function sum(...numbers) {
    return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);  // 10

// 解构中的剩余
const [first, ...rest] = [1, 2, 3, 4];
const { a, ...others } = { a: 1, b: 2, c: 3 };

可选链(Optional Chaining):

const user = {
    name: 'John',
    address: {
        city: 'NYC'
    }
};

// 安全访问嵌套属性
user?.address?.city      // 'NYC'
user?.contact?.email     // undefined(不会报错)

// 安全调用方法
user.getName?.()         // undefined

// 安全访问数组
const arr = [1, 2, 3];
arr?.[0]                 // 1

空值合并(Nullish Coalescing):

// ?? 只在 null 或 undefined 时使用默认值
const value1 = null ?? 'default';      // 'default'
const value2 = undefined ?? 'default'; // 'default'
const value3 = 0 ?? 'default';         // 0(0 不是 nullish)
const value4 = '' ?? 'default';        // ''(空字符串不是 nullish)

// 与 || 的区别
0 || 'default'   // 'default'(0 是 falsy)
0 ?? 'default'   // 0(0 不是 nullish)

Promise 与异步:

// Promise 创建
const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve('done'), 1000);
});

// Promise 使用
promise
    .then(result => console.log(result))
    .catch(error => console.error(error))
    .finally(() => console.log('完成'));

// async/await
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('请求失败:', error);
    }
}

// 并行执行
const [user, posts] = await Promise.all([
    fetchUser(),
    fetchPosts()
]);

迭代器与生成器:

// 生成器函数
function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = numberGenerator();
gen.next();  // { value: 1, done: false }
gen.next();  // { value: 2, done: false }
gen.next();  // { value: 3, done: false }
gen.next();  // { value: undefined, done: true }

// 异步生成器
async function* asyncGenerator() {
    yield await fetchData1();
    yield await fetchData2();
}

// for await...of
for await (const data of asyncGenerator()) {
    console.log(data);
}

类与面向对象

类定义:

class Animal {
    // 私有字段(ES2022)
    #privateField = 'private';
    
    // 静态属性
    static kingdom = 'Animalia';
    
    // 构造函数
    constructor(name) {
        this.name = name;
    }
    
    // 实例方法
    speak() {
        console.log(`${this.name} makes a sound`);
    }
    
    // 静态方法
    static create(name) {
        return new Animal(name);
    }
    
    // Getter
    get info() {
        return `Animal: ${this.name}`;
    }
    
    // Setter
    set nickname(value) {
        this._nickname = value;
    }
}

类继承:

class Dog extends Animal {
    constructor(name, breed) {
        super(name);  // 调用父类构造函数
        this.breed = breed;
    }
    
    // 方法重写
    speak() {
        console.log(`${this.name} barks`);
    }
    
    // 调用父类方法
    parentSpeak() {
        super.speak();
    }
}

const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak();  // 'Buddy barks'

类特性对比:

特性ES5 构造函数ES6 类
语法function Fn() {}class Fn {}
继承原型链 + callextends + super
静态方法Fn.method = ...static method()
私有属性闭包模拟#field
Getter/SetterObject.definePropertyget/set

Mixin 模式:

// 定义 Mixin
const Flyable = {
    fly() {
        console.log(`${this.name} is flying`);
    }
};

const Swimmable = {
    swim() {
        console.log(`${this.name} is swimming`);
    }
};

// 应用 Mixin
class Duck extends Animal {}
Object.assign(Duck.prototype, Flyable, Swimmable);

const duck = new Duck('Donald');
duck.fly();   // 'Donald is flying'
duck.swim();  // 'Donald is swimming'

常用设计模式:

模式说明应用场景
单例模式确保只有一个实例配置管理、数据库连接
工厂模式创建对象的接口对象创建逻辑复杂时
观察者模式发布-订阅事件系统、状态管理
策略模式封装算法多种算法切换
装饰器模式动态添加功能功能增强
// 单例模式
class Database {
    static #instance;
    
    constructor() {
        if (Database.#instance) {
            return Database.#instance;
        }
        Database.#instance = this;
    }
    
    static getInstance() {
        if (!Database.#instance) {
            Database.#instance = new Database();
        }
        return Database.#instance;
    }
}

// 观察者模式
class EventEmitter {
    #events = {};
    
    on(event, listener) {
        if (!this.#events[event]) {
            this.#events[event] = [];
        }
        this.#events[event].push(listener);
    }
    
    emit(event, ...args) {
        if (this.#events[event]) {
            this.#events[event].forEach(listener => listener(...args));
        }
    }
}

Node.js 核心架构

V8引擎

V8 是 Google 开发的高性能 JavaScript 和 WebAssembly 引擎,用 C++ 编写,是 Node.js 的核心组件之一。

V8 引擎核心特性:

特性说明
JIT 编译即时编译,将 JavaScript 直接编译为机器码
隐藏类优化对象属性访问速度
内联缓存缓存方法调用,提高执行效率
垃圾回收分代式垃圾回收,自动内存管理
优化编译器TurboFan 优化热点代码

V8 执行流程:

graph LR subgraph "V8 编译流程" A["JavaScript
源代码"] --> B["Parser
解析器"] B --> C["AST
抽象语法树"] C --> D["Ignition
解释器"] D --> E["字节码"] E --> F{"热点代码?"} F -->|是| G["TurboFan
优化编译器"] G --> H["优化机器码"] F -->|否| I["解释执行"] end style A fill:#e3f2fd,stroke:#333 style D fill:#fff9c4,stroke:#333 style G fill:#c8e6c9,stroke:#333 style H fill:#ffccbc,stroke:#333

V8 内存结构:

V8 的内存分为以下几个区域:

内存区域用途特点
新生代(New Space)存放新创建的对象空间小,GC 频繁
老生代(Old Space)存放存活时间长的对象空间大,GC 较少
大对象空间(Large Object Space)存放超过阈值的大对象不会被移动
代码空间(Code Space)存放 JIT 编译的代码可执行内存
Map 空间存放对象的 Map 信息隐藏类信息

垃圾回收机制:

graph TB subgraph "新生代 GC - Scavenge" A["From 空间"] -->|复制存活对象| B["To 空间"] B -->|角色互换| A end subgraph "老生代 GC - Mark-Sweep-Compact" C["标记阶段"] --> D["清除阶段"] D --> E["整理阶段"] end subgraph "晋升条件" F["经历多次 Scavenge"] G["To 空间使用超过 25%"] end A -.->|晋升| C F --> A G --> A style A fill:#bbdefb,stroke:#333 style B fill:#c8e6c9,stroke:#333 style C fill:#fff9c4,stroke:#333

事件循环机制

事件循环(Event Loop)是 Node.js 实现非阻塞 I/O 的核心机制,理解事件循环对于编写高性能 Node.js 应用至关重要。

事件循环的六个阶段:

graph TB subgraph "Event Loop 阶段" A["timers
执行setTimeout/setInterval回调"] --> B["pending callbacks
执行系统操作回调"] B --> C["idle, prepare
内部使用"] C --> D["poll
获取新I/O事件"] D --> E["check
执行setImmediate回调"] E --> F["close callbacks
执行close事件回调"] F --> A end G["process.nextTick"] -.->|每个阶段之间执行| A H["Promise.then"] -.->|微任务队列| A style A fill:#ffcdd2,stroke:#333 style D fill:#c8e6c9,stroke:#333 style E fill:#bbdefb,stroke:#333

各阶段详解:

阶段执行内容说明
timerssetTimeout、setInterval 回调执行已到期的定时器回调
pending callbacks某些系统操作的回调如 TCP 错误回调
idle, prepare内部使用仅供 Node.js 内部使用
pollI/O 回调最重要的阶段,处理 I/O 事件
checksetImmediate 回调在 poll 阶段完成后立即执行
close callbacksclose 事件回调如 socket.on(‘close’)

微任务与宏任务:

graph LR subgraph "任务优先级" A["同步代码"] --> B["process.nextTick"] B --> C["Promise.then
微任务"] C --> D["setTimeout
setInterval"] D --> E["setImmediate"] E --> F["I/O 回调"] end style A fill:#f44336,stroke:#333,color:#fff style B fill:#ff9800,stroke:#333 style C fill:#ffeb3b,stroke:#333 style D fill:#4caf50,stroke:#333,color:#fff style E fill:#2196f3,stroke:#333,color:#fff

执行顺序示例分析:

console.log('1. 同步代码');

setTimeout(() => console.log('2. setTimeout'), 0);

setImmediate(() => console.log('3. setImmediate'));

Promise.resolve().then(() => console.log('4. Promise'));

process.nextTick(() => console.log('5. nextTick'));

console.log('6. 同步代码结束');

// 输出顺序:1 -> 6 -> 5 -> 4 -> 2 -> 3

关键理解点:

  1. process.nextTick 优先级最高,在当前操作完成后立即执行
  2. Promise.then 属于微任务,在 nextTick 之后执行
  3. setTimeout(fn, 0)setImmediate 的执行顺序不确定(在主模块中)
  4. I/O 回调中,setImmediate 总是先于 setTimeout 执行

libuv库

libuv 是一个跨平台的异步 I/O 库,为 Node.js 提供了事件循环和异步 I/O 的底层实现。

libuv 核心功能:

功能模块说明
事件循环实现 Node.js 的事件循环机制
异步文件 I/O通过线程池实现异步文件操作
异步网络 I/O使用系统原生异步机制(epoll/kqueue/IOCP)
线程池处理无法异步化的操作
定时器实现 setTimeout/setInterval
子进程进程创建和管理
信号处理Unix 信号处理

libuv 架构图:

graph TB subgraph "Node.js 应用层" A["JavaScript 代码"] end subgraph "Node.js Bindings" B["Node.js C++ Bindings"] end subgraph "libuv" C["Event Loop"] D["Thread Pool
默认4线程"] E["Network I/O"] F["File I/O"] G["DNS"] end subgraph "操作系统" H["epoll/kqueue/IOCP"] I["文件系统"] J["网络栈"] end A --> B B --> C C --> D C --> E D --> F D --> G E --> H F --> I H --> J style A fill:#e3f2fd,stroke:#333 style C fill:#fff9c4,stroke:#333 style D fill:#c8e6c9,stroke:#333 style H fill:#ffccbc,stroke:#333

线程池工作原理:

libuv 维护一个默认 4 个线程的线程池,用于处理以下操作:

  • 文件系统操作(fs 模块)
  • DNS 查询(dns.lookup)
  • 某些加密操作
  • 压缩操作

可以通过环境变量调整线程池大小:

# 设置线程池大小为 8
UV_THREADPOOL_SIZE=8 node app.js

网络 I/O vs 文件 I/O:

类型实现方式原因
网络 I/O系统原生异步(epoll等)操作系统提供异步支持
文件 I/O线程池模拟异步大多数系统不支持真正的异步文件 I/O

Node.js运行时架构

完整架构图:

graph TB subgraph "应用层" A["Node.js 应用代码"] B["npm 第三方模块"] end subgraph "Node.js 核心" C["Node.js Core Modules
fs, http, path, events..."] D["Node.js Bindings
C++ 绑定层"] end subgraph "底层依赖" E["V8 Engine
JavaScript 引擎"] F["libuv
异步 I/O"] G["c-ares
异步 DNS"] H["OpenSSL
加密"] I["zlib
压缩"] J["http-parser
HTTP 解析"] end subgraph "操作系统" K["Linux/Windows/macOS"] end A --> C B --> C C --> D D --> E D --> F D --> G D --> H D --> I D --> J E --> K F --> K style A fill:#e3f2fd,stroke:#333 style C fill:#c8e6c9,stroke:#333 style E fill:#fff9c4,stroke:#333 style F fill:#ffccbc,stroke:#333

各层职责:

层级组件职责
应用层用户代码、npm 模块业务逻辑实现
核心模块层fs、http、path 等提供 JavaScript API
绑定层C++ Bindings连接 JS 和 C++
底层依赖V8、libuv 等提供核心能力
操作系统Linux/Windows/macOS系统调用

单线程与多进程模型

单线程的理解:

Node.js 的"单线程"指的是执行 JavaScript 代码的主线程是单线程,但 Node.js 本身是多线程的:

graph TB subgraph "Node.js 进程" A["主线程
执行 JS 代码
事件循环"] subgraph "libuv 线程池" B["Worker 1"] C["Worker 2"] D["Worker 3"] E["Worker 4"] end F["V8 GC 线程"] G["V8 编译线程"] end A --> B A --> C A --> D A --> E style A fill:#f44336,stroke:#333,color:#fff style B fill:#4caf50,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#4caf50,stroke:#333,color:#fff style E fill:#4caf50,stroke:#333,color:#fff

单线程的优缺点:

优点缺点
无需处理线程同步CPU 密集型任务会阻塞
内存占用低无法利用多核 CPU
无死锁问题单点故障风险
上下文切换开销小错误处理需谨慎

多进程解决方案:

为了充分利用多核 CPU,Node.js 提供了多种多进程方案:

1. cluster 模块

graph TB subgraph "Cluster 架构" A["Master 进程
管理 Worker"] B["Worker 1
处理请求"] C["Worker 2
处理请求"] D["Worker 3
处理请求"] E["Worker 4
处理请求"] end F["客户端请求"] --> A A -->|负载均衡| B A -->|负载均衡| C A -->|负载均衡| D A -->|负载均衡| E style A fill:#ff9800,stroke:#333 style B fill:#4caf50,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#4caf50,stroke:#333,color:#fff style E fill:#4caf50,stroke:#333,color:#fff

2. child_process 模块

用于创建子进程执行任务:

方法用途特点
spawn执行命令流式输出,适合大量数据
exec执行命令缓冲输出,有大小限制
execFile执行文件不启动 shell
fork创建 Node 进程内置 IPC 通道

3. worker_threads 模块

Node.js 10+ 引入的真正的多线程支持:

graph LR subgraph "主线程" A["Main Thread"] end subgraph "Worker 线程" B["Worker Thread 1"] C["Worker Thread 2"] end A -->|postMessage| B A -->|postMessage| C B -->|postMessage| A C -->|postMessage| A D["SharedArrayBuffer
共享内存"] --> A D --> B D --> C style A fill:#2196f3,stroke:#333,color:#fff style B fill:#4caf50,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#ff9800,stroke:#333

多进程方案对比:

方案适用场景通信方式内存共享
clusterWeb 服务器扩展IPC
child_process执行外部命令/脚本IPC/stdio
worker_threadsCPU 密集型计算postMessageSharedArrayBuffer

Node.js 核心模块

Node.js 提供了丰富的内置模块,无需安装即可使用。这些模块是构建 Node.js 应用的基础。

文件系统模块(fs)

fs 模块提供了与文件系统交互的 API,支持同步和异步两种操作方式。

常用 API 分类:

类别同步方法异步方法Promise 方法
读取文件readFileSyncreadFilefs.promises.readFile
写入文件writeFileSyncwriteFilefs.promises.writeFile
追加内容appendFileSyncappendFilefs.promises.appendFile
删除文件unlinkSyncunlinkfs.promises.unlink
创建目录mkdirSyncmkdirfs.promises.mkdir
读取目录readdirSyncreaddirfs.promises.readdir
文件信息statSyncstatfs.promises.stat
重命名renameSyncrenamefs.promises.rename

三种使用方式对比:

// 1. 回调方式(传统)
fs.readFile('file.txt', 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);
});

// 2. 同步方式(阻塞)
const data = fs.readFileSync('file.txt', 'utf8');

// 3. Promise 方式(推荐)
const data = await fs.promises.readFile('file.txt', 'utf8');

文件操作最佳实践:

场景推荐方式原因
启动时读取配置同步方式简单,启动时阻塞可接受
运行时文件操作Promise/异步不阻塞事件循环
大文件处理Stream内存友好
批量文件操作Promise.all并行处理

文件监听:

// 监听文件变化
fs.watch('file.txt', (eventType, filename) => {
    console.log(`${filename} 发生了 ${eventType} 事件`);
});

// 更精确的监听(轮询方式)
fs.watchFile('file.txt', { interval: 1000 }, (curr, prev) => {
    console.log('文件被修改');
});

路径模块(path)

path 模块提供了处理文件和目录路径的工具,跨平台兼容是其核心价值。

核心方法:

方法功能示例
path.join()拼接路径path.join('/a', 'b', 'c')/a/b/c
path.resolve()解析为绝对路径path.resolve('a', 'b')/当前目录/a/b
path.dirname()获取目录名path.dirname('/a/b/c.js')/a/b
path.basename()获取文件名path.basename('/a/b/c.js')c.js
path.extname()获取扩展名path.extname('file.txt').txt
path.parse()解析路径返回 {root, dir, base, ext, name}
path.format()格式化路径将对象转为路径字符串
path.isAbsolute()是否绝对路径返回 boolean
path.relative()计算相对路径从一个路径到另一个的相对路径

跨平台注意事项:

// Windows 和 Unix 路径分隔符不同
path.sep  // Windows: '\\'  Unix: '/'

// 始终使用 path.join 而不是字符串拼接
// ❌ 错误
const filePath = dir + '/' + filename;

// ✅ 正确
const filePath = path.join(dir, filename);

常用路径变量:

变量说明
__dirname当前模块所在目录的绝对路径
__filename当前模块文件的绝对路径
process.cwd()当前工作目录

HTTP模块

http 模块是构建 Web 服务器的基础模块。

创建 HTTP 服务器:

graph LR subgraph "HTTP 服务器处理流程" A["客户端请求"] --> B["http.Server"] B --> C["request 事件"] C --> D["处理请求"] D --> E["发送响应"] E --> F["客户端"] end style B fill:#4caf50,stroke:#333,color:#fff style C fill:#ff9800,stroke:#333

请求对象(req)常用属性:

属性/方法说明
req.url请求 URL
req.method请求方法(GET/POST等)
req.headers请求头对象
req.httpVersionHTTP 版本
req.on(‘data’)接收请求体数据
req.on(’end’)请求体接收完成

响应对象(res)常用方法:

方法说明
res.writeHead()设置状态码和响应头
res.setHeader()设置单个响应头
res.write()写入响应体
res.end()结束响应
res.statusCode设置状态码

HTTP 客户端请求:

// 发起 GET 请求
http.get('http://api.example.com/data', (res) => {
    let data = '';
    res.on('data', chunk => data += chunk);
    res.on('end', () => console.log(data));
});

// 发起 POST 请求
const req = http.request({
    hostname: 'api.example.com',
    method: 'POST',
    headers: { 'Content-Type': 'application/json' }
}, (res) => {
    // 处理响应
});
req.write(JSON.stringify({ key: 'value' }));
req.end();

事件模块(events)

events 模块是 Node.js 事件驱动架构的核心,几乎所有 Node.js 核心模块都继承自 EventEmitter。

EventEmitter 核心方法:

方法说明
on(event, listener)注册事件监听器
once(event, listener)注册一次性监听器
emit(event, …args)触发事件
off(event, listener)移除监听器
removeAllListeners()移除所有监听器
listeners(event)获取监听器数组
listenerCount(event)获取监听器数量

事件机制流程:

graph TB subgraph "EventEmitter 工作原理" A["注册监听器
emitter.on('event', fn)"] --> B["事件队列
存储监听器"] C["触发事件
emitter.emit('event')"] --> D["遍历执行
所有监听器"] B --> D end style A fill:#e3f2fd,stroke:#333 style C fill:#c8e6c9,stroke:#333 style D fill:#fff9c4,stroke:#333

自定义事件类:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {
    doSomething() {
        this.emit('start');
        // 执行操作
        this.emit('end', { result: 'success' });
    }
}

const myEmitter = new MyEmitter();
myEmitter.on('start', () => console.log('开始'));
myEmitter.on('end', (data) => console.log('结束', data));

错误事件处理:

// 必须监听 error 事件,否则会抛出异常
emitter.on('error', (err) => {
    console.error('发生错误:', err);
});

流模块(stream)

Stream 是 Node.js 处理流式数据的抽象接口,适合处理大量数据而不占用大量内存。

四种流类型:

类型说明示例
Readable可读流fs.createReadStream、http.IncomingMessage
Writable可写流fs.createWriteStream、http.ServerResponse
Duplex双工流(可读可写)net.Socket、TCP 连接
Transform转换流zlib.createGzip、crypto 流

流的工作模式:

graph LR subgraph "流式处理" A["数据源"] -->|chunk| B["Readable Stream"] B -->|chunk| C["Transform Stream"] C -->|chunk| D["Writable Stream"] D -->|chunk| E["目标"] end style B fill:#4caf50,stroke:#333,color:#fff style C fill:#ff9800,stroke:#333 style D fill:#2196f3,stroke:#333,color:#fff

流的两种模式:

模式说明触发方式
流动模式(flowing)数据自动流动监听 data 事件或调用 pipe()
暂停模式(paused)需手动读取调用 read() 方法

pipe 管道操作:

// 文件复制(内存友好)
const readable = fs.createReadStream('source.txt');
const writable = fs.createWriteStream('dest.txt');
readable.pipe(writable);

// 链式管道
fs.createReadStream('file.txt')
    .pipe(zlib.createGzip())
    .pipe(fs.createWriteStream('file.txt.gz'));

背压(Backpressure)处理:

当写入速度跟不上读取速度时,需要处理背压:

const readable = fs.createReadStream('large-file.txt');
const writable = fs.createWriteStream('output.txt');

readable.on('data', (chunk) => {
    const canContinue = writable.write(chunk);
    if (!canContinue) {
        readable.pause();  // 暂停读取
    }
});

writable.on('drain', () => {
    readable.resume();  // 恢复读取
});

Buffer与二进制处理

Buffer 是 Node.js 用于处理二进制数据的类,在处理文件、网络数据时经常使用。

Buffer 创建方式:

方法说明示例
Buffer.alloc(size)创建指定大小的 Buffer(初始化为0)Buffer.alloc(10)
Buffer.allocUnsafe(size)创建未初始化的 Buffer(更快但不安全)Buffer.allocUnsafe(10)
Buffer.from(array)从数组创建Buffer.from([1, 2, 3])
Buffer.from(string)从字符串创建Buffer.from('hello')
Buffer.from(buffer)复制 BufferBuffer.from(buf)

Buffer 常用操作:

操作方法说明
写入buf.write(string)写入字符串
读取buf.toString(encoding)转为字符串
切片buf.slice(start, end)获取子 Buffer
拼接Buffer.concat([buf1, buf2])合并多个 Buffer
比较buf.compare(otherBuf)比较两个 Buffer
查找buf.indexOf(value)查找值的位置
填充buf.fill(value)填充 Buffer

编码支持:

编码说明
utf8默认编码,多字节 Unicode
ascii7 位 ASCII
base64Base64 编码
hex十六进制
binary二进制(已废弃,使用 latin1)
latin1ISO-8859-1

Buffer 与 TypedArray:

Node.js 的 Buffer 是 Uint8Array 的子类,可以与 TypedArray 互操作:

const buf = Buffer.from([1, 2, 3, 4]);
const arr = new Uint8Array(buf);  // 共享内存

// 从 ArrayBuffer 创建
const arrayBuffer = new ArrayBuffer(16);
const buffer = Buffer.from(arrayBuffer);

异步编程模型

异步编程是 Node.js 的核心特性,掌握各种异步模式对于编写高质量的 Node.js 代码至关重要。

回调函数模式

回调函数是 Node.js 最早的异步处理方式,遵循错误优先回调(Error-First Callback) 约定。

错误优先回调约定:

// 回调函数的第一个参数是错误对象
function callback(err, result) {
    if (err) {
        // 处理错误
        return;
    }
    // 处理结果
}

回调地狱问题:

graph TB subgraph "回调地狱示意" A["操作1"] --> B["回调1"] B --> C["操作2"] C --> D["回调2"] D --> E["操作3"] E --> F["回调3"] F --> G["..."] end style A fill:#f44336,stroke:#333,color:#fff style C fill:#ff9800,stroke:#333 style E fill:#ffeb3b,stroke:#333

回调地狱的问题:

问题说明
代码嵌套深多层回调导致代码难以阅读
错误处理分散每层都需要处理错误
流程控制困难难以实现并行、串行控制
调试困难堆栈信息不完整

解决回调地狱的方法:

  1. 命名函数:将回调提取为命名函数
  2. 模块化:将逻辑拆分到不同模块
  3. 使用 Promise:现代推荐方式
  4. 使用 async/await:最佳实践

Promise与async/await

Promise 基础:

Promise 是异步操作的最终完成或失败的表示,有三种状态:

状态说明转换
pending初始状态可转为 fulfilled 或 rejected
fulfilled操作成功不可再变
rejected操作失败不可再变
graph LR A["pending
等待中"] -->|resolve| B["fulfilled
已完成"] A -->|reject| C["rejected
已拒绝"] style A fill:#ffeb3b,stroke:#333 style B fill:#4caf50,stroke:#333,color:#fff style C fill:#f44336,stroke:#333,color:#fff

Promise 链式调用:

fetchUser(userId)
    .then(user => fetchOrders(user.id))
    .then(orders => processOrders(orders))
    .then(result => console.log(result))
    .catch(err => console.error(err))
    .finally(() => cleanup());

Promise 静态方法:

方法说明使用场景
Promise.all()所有 Promise 都成功才成功并行执行,全部需要
Promise.allSettled()等待所有 Promise 完成需要所有结果,不管成败
Promise.race()返回最先完成的结果超时控制、竞速
Promise.any()返回第一个成功的结果多源获取,取最快成功的
Promise.resolve()创建已完成的 Promise包装同步值
Promise.reject()创建已拒绝的 Promise创建错误 Promise

async/await 语法:

async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:

// Promise 方式
function getData() {
    return fetch('/api/data')
        .then(res => res.json())
        .then(data => processData(data));
}

// async/await 方式(推荐)
async function getData() {
    const res = await fetch('/api/data');
    const data = await res.json();
    return processData(data);
}

async/await 错误处理:

// 方式1:try-catch
async function getData() {
    try {
        const data = await fetchData();
        return data;
    } catch (err) {
        console.error('获取数据失败:', err);
        throw err;
    }
}

// 方式2:.catch()
async function getData() {
    const data = await fetchData().catch(err => {
        console.error(err);
        return null;
    });
    return data;
}

并行与串行执行:

// 串行执行(一个接一个)
async function serial() {
    const a = await taskA();  // 等待完成
    const b = await taskB();  // 再执行
    return [a, b];
}

// 并行执行(同时进行)
async function parallel() {
    const [a, b] = await Promise.all([
        taskA(),
        taskB()
    ]);
    return [a, b];
}

常见陷阱:

陷阱问题解决方案
循环中的 await串行执行,性能差使用 Promise.all
忘记 awaitPromise 未等待确保添加 await
错误未捕获未处理的 rejection添加 try-catch 或 .catch
async 函数返回值总是返回 Promise注意调用方式

事件驱动编程

事件驱动是 Node.js 的核心编程范式,基于发布-订阅模式

事件驱动架构:

graph TB subgraph "事件驱动模型" A["事件发射器
EventEmitter"] --> B["事件队列"] C["事件监听器1"] --> B D["事件监听器2"] --> B E["事件监听器3"] --> B B --> F["事件循环"] F --> G["执行回调"] end style A fill:#4caf50,stroke:#333,color:#fff style B fill:#ff9800,stroke:#333 style F fill:#2196f3,stroke:#333,color:#fff

事件驱动 vs 传统请求-响应:

特性事件驱动请求-响应
耦合度松耦合紧耦合
扩展性易于扩展扩展困难
异步支持天然异步需要额外处理
调试难度较高较低

事件驱动最佳实践:

  1. 合理命名事件:使用清晰、一致的命名
  2. 限制监听器数量:避免内存泄漏
  3. 错误事件必须处理:否则会抛出异常
  4. 使用 once 处理一次性事件:避免重复触发
// 设置最大监听器数量
emitter.setMaxListeners(20);

// 检查监听器数量
if (emitter.listenerCount('event') > 10) {
    console.warn('监听器过多');
}

错误处理机制

Node.js 中的错误处理需要特别注意,因为未捕获的错误会导致进程崩溃

错误类型:

类型说明处理方式
同步错误同步代码中的错误try-catch
异步回调错误回调函数中的错误错误优先回调
Promise 错误Promise 中的错误.catch() 或 try-catch
事件错误EventEmitter 错误error 事件监听
未捕获异常未处理的错误uncaughtException
未处理的 rejection未处理的 Promise 拒绝unhandledRejection

全局错误处理:

// 捕获未处理的异常
process.on('uncaughtException', (err) => {
    console.error('未捕获的异常:', err);
    // 记录日志后退出
    process.exit(1);
});

// 捕获未处理的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
    console.error('未处理的 Promise 拒绝:', reason);
});

错误处理最佳实践:

graph TB subgraph "错误处理策略" A["错误发生"] --> B{"错误类型"} B -->|可恢复| C["记录日志
继续运行"] B -->|不可恢复| D["记录日志
优雅退出"] B -->|编程错误| E["修复代码"] end style A fill:#f44336,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#ff9800,stroke:#333 style E fill:#2196f3,stroke:#333,color:#fff

自定义错误类:

class AppError extends Error {
    constructor(message, statusCode, isOperational = true) {
        super(message);
        this.statusCode = statusCode;
        this.isOperational = isOperational;  // 区分操作错误和编程错误
        Error.captureStackTrace(this, this.constructor);
    }
}

// 使用
throw new AppError('用户不存在', 404);

错误处理原则:

原则说明
快速失败发现错误立即处理,不要忽略
区分错误类型操作错误 vs 编程错误
提供有意义的错误信息便于调试和用户理解
不要吞掉错误至少记录日志
优雅降级部分功能失败不影响整体

包管理与模块系统

npm包管理器

npm(Node Package Manager)是 Node.js 的默认包管理器,也是世界上最大的软件注册表。

npm 核心功能:

功能命令说明
安装包npm install <pkg>安装依赖包
全局安装npm install -g <pkg>安装全局命令行工具
卸载包npm uninstall <pkg>移除依赖包
更新包npm update <pkg>更新依赖包
查看信息npm info <pkg>查看包信息
搜索包npm search <keyword>搜索包
发布包npm publish发布自己的包
运行脚本npm run <script>执行 package.json 中的脚本

依赖类型:

类型安装命令说明使用场景
dependenciesnpm i <pkg>生产依赖运行时需要
devDependenciesnpm i -D <pkg>开发依赖仅开发时需要
peerDependencies手动配置同伴依赖插件声明宿主版本
optionalDependenciesnpm i -O <pkg>可选依赖安装失败不影响

版本号规范(SemVer):

主版本号.次版本号.修订号
  MAJOR.MINOR.PATCH
符号含义示例
^兼容次版本更新^1.2.31.x.x
~兼容修订版本更新~1.2.31.2.x
>大于指定版本>1.2.3
>=大于等于>=1.2.3
<小于<1.2.3
=精确版本=1.2.31.2.3
*****任意版本*
x通配符1.x

npm 常用命令速查:

# 初始化项目
npm init -y

# 安装所有依赖
npm install

# 清理缓存
npm cache clean --force

# 查看全局安装位置
npm root -g

# 查看过期的包
npm outdated

# 安全审计
npm audit

# 修复安全问题
npm audit fix

# 查看包的依赖树
npm ls

# 链接本地包(开发调试)
npm link

npm vs yarn vs pnpm:

特性npmyarnpnpm
速度较慢最快
磁盘空间占用大占用大节省空间
锁文件package-lock.jsonyarn.lockpnpm-lock.yaml
工作区npm workspacesyarn workspacespnpm workspaces
安全性一般较好较好
幽灵依赖存在存在不存在

npx 工具链

npx 核心机制

npx 是 Node.js 自带的包执行工具,其设计目标是 “无需安装,直接运行” npm 注册表中的包。

工作原理

  1. 检查本地 node_modules/.bin 目录是否存在该命令。
  2. 若本地不存在,则从 npm 仓库临时下载指定的包到缓存目录。
  3. 执行该包中的命令。
  4. 执行完毕后,不进行全局安装,保持环境清洁。

npm 的区别

命令角色关键行为
npm install包管理工具将包安装到本地 node_modules 或全局。
npx <command>包执行工具临时运行包中的命令,优先使用本地,其次远程拉取。

典型使用场景

  • 运行一次性脚手架工具npx create-react-app my-app
  • 运行项目本地依赖的命令npx eslint src/
  • 指定版本运行npx [email protected] tsc --init
  • 执行远程脚本npx degit user/repo my-project

局限性npx 不适合运行需要长时间驻留、消耗大量资源的服务(如本地大模型),因为这违背其“临时执行”的设计初衷,容易因资源问题失败。

npx wrangler 详解

npx wrangler 是使用 npx 运行 Cloudflare 的官方命令行工具 wrangler,用于开发和部署 Cloudflare 生态的各项服务。

wrangler 核心功能

功能说明
Workers开发、调试、部署边缘 JavaScript/WebAssembly 函数。
Pages管理 Pages 项目及 Pages Functions。
KV操作 Key-Value 存储命名空间。
R2管理对象存储桶和文件。
D1操作 SQLite 数据库。
Queues管理消息队列。

常用命令

# 1. 初始化一个 Worker 项目
npx wrangler init my-worker
# 2. 本地开发调试(启动本地模拟环境)
npx wrangler dev
# 3. 部署到 Cloudflare 全球网络
npx wrangler deploy
# 4. 登录 Cloudflare 账号
npx wrangler login

环境适配:在云 IDE 中使用 npx wrangler dev 是合理的,因为 Workers 是轻量的边缘计算模型,符合云 IDE 的资源限制。但若试图在 Worker 中运行重计算模型,则会遭遇与之前所述相同的资源瓶颈。

CommonJS模块规范

CommonJS 是 Node.js 的原生模块系统,使用 requiremodule.exports

模块导出方式:

// 方式1:module.exports 导出对象
module.exports = {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b
};

// 方式2:module.exports 导出单个值
module.exports = function add(a, b) {
    return a + b;
};

// 方式3:exports 快捷方式
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;

模块导入方式:

// 导入整个模块
const math = require('./math');
math.add(1, 2);

// 解构导入
const { add, subtract } = require('./math');
add(1, 2);

// 导入核心模块
const fs = require('fs');

// 导入 node_modules 中的包
const express = require('express');

模块解析规则:

graph TB A["require('module')"] --> B{"是否核心模块?"} B -->|是| C["返回核心模块"] B -->|否| D{"以 ./ 或 ../ 开头?"} D -->|是| E["作为文件/目录解析"] D -->|否| F["在 node_modules 中查找"] E --> G["尝试 .js, .json, .node"] E --> H["尝试 目录/index.js"] F --> I["向上遍历 node_modules"] style A fill:#e3f2fd,stroke:#333 style C fill:#c8e6c9,stroke:#333 style E fill:#fff9c4,stroke:#333 style F fill:#ffccbc,stroke:#333

模块缓存机制:

// 模块只会加载一次,后续 require 返回缓存
const a = require('./module');
const b = require('./module');
console.log(a === b);  // true

// 查看缓存
console.log(require.cache);

// 清除缓存(谨慎使用)
delete require.cache[require.resolve('./module')];

CommonJS 特点:

特点说明
同步加载require 是同步的
运行时加载模块在运行时解析
值拷贝导出的是值的拷贝
单例模式模块只执行一次
动态导入可以在任意位置 require

ES Modules

ES Modules(ESM)是 JavaScript 的官方标准模块系统,Node.js 12+ 开始稳定支持。

启用 ESM 的方式:

方式说明
文件扩展名 .mjs单个文件使用 ESM
package.json 设置 type: “module”整个项目使用 ESM

ESM 导出语法:

// 命名导出
export const name = 'Node.js';
export function add(a, b) { return a + b; }

// 默认导出
export default class Calculator { }

// 混合导出
export { name, add };
export default Calculator;

// 重命名导出
export { add as sum };

ESM 导入语法:

// 命名导入
import { name, add } from './module.js';

// 默认导入
import Calculator from './module.js';

// 混合导入
import Calculator, { name, add } from './module.js';

// 重命名导入
import { add as sum } from './module.js';

// 导入所有
import * as math from './module.js';

// 动态导入
const module = await import('./module.js');

CommonJS vs ES Modules:

特性CommonJSES Modules
语法require/module.exportsimport/export
加载方式同步异步
加载时机运行时编译时(静态分析)
导出值值拷贝值引用(实时绑定)
顶层 thismodule.exportsundefined
动态导入require()import()
Tree Shaking不支持支持
循环依赖部分支持完全支持

ESM 中使用 CommonJS:

// ESM 中导入 CommonJS 模块
import pkg from 'commonjs-package';

// 使用 createRequire
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const pkg = require('commonjs-package');

ESM 特有功能:

// import.meta 元信息
console.log(import.meta.url);  // 当前模块的 URL

// 顶层 await(Node.js 14.8+)
const data = await fetch('/api/data');

package.json配置详解

package.json 是 Node.js 项目的核心配置文件,定义了项目的元数据和依赖。

核心字段:

字段说明示例
name包名称"my-package"
version版本号"1.0.0"
description描述"A sample package"
mainCommonJS 入口"index.js"
moduleESM 入口"index.mjs"
exports导出映射见下文
type模块类型"module""commonjs"
scripts脚本命令{"start": "node app.js"}
dependencies生产依赖{"express": "^4.18.0"}
devDependencies开发依赖{"jest": "^29.0.0"}
enginesNode 版本要求{"node": ">=16.0.0"}
files发布包含的文件["dist", "lib"]
bin可执行文件{"cli": "./bin/cli.js"}

exports 字段(条件导出):

{
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    },
    "./utils": {
      "import": "./dist/utils.mjs",
      "require": "./dist/utils.cjs"
    }
  }
}

scripts 常用配置:

{
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js",
    "build": "tsc",
    "test": "jest",
    "lint": "eslint src/",
    "preinstall": "echo 安装前执行",
    "postinstall": "echo 安装后执行",
    "prepublishOnly": "npm run build"
  }
}

生命周期脚本:

graph LR subgraph "npm install 生命周期" A["preinstall"] --> B["install"] B --> C["postinstall"] end subgraph "npm publish 生命周期" D["prepublishOnly"] --> E["prepare"] E --> F["prepublish"] F --> G["publish"] G --> H["postpublish"] end style A fill:#e3f2fd,stroke:#333 style D fill:#c8e6c9,stroke:#333

workspaces(Monorepo 支持):

{
  "workspaces": [
    "packages/*"
  ]
}

Web开发框架

Express框架

Express 是 Node.js 最流行的 Web 框架,以简洁、灵活著称,是学习 Node.js Web 开发的首选。

Express 核心概念:

graph LR subgraph "Express 请求处理流程" A["HTTP 请求"] --> B["中间件1"] B --> C["中间件2"] C --> D["路由处理"] D --> E["中间件3"] E --> F["HTTP 响应"] end style A fill:#e3f2fd,stroke:#333 style D fill:#c8e6c9,stroke:#333 style F fill:#ffccbc,stroke:#333

中间件机制:

中间件是 Express 的核心,每个中间件可以:

  • 执行任何代码
  • 修改请求和响应对象
  • 结束请求-响应循环
  • 调用下一个中间件
中间件类型说明示例
应用级中间件绑定到 app 实例app.use(middleware)
路由级中间件绑定到 router 实例router.use(middleware)
错误处理中间件处理错误app.use((err, req, res, next) => {})
内置中间件Express 内置express.json(), express.static()
第三方中间件npm 安装cors, helmet, morgan

常用内置中间件:

中间件功能
express.json()解析 JSON 请求体
express.urlencoded()解析 URL 编码请求体
express.static()提供静态文件服务
express.Router()创建模块化路由

路由定义:

// 基本路由
app.get('/users', (req, res) => { });
app.post('/users', (req, res) => { });
app.put('/users/:id', (req, res) => { });
app.delete('/users/:id', (req, res) => { });

// 路由参数
app.get('/users/:id', (req, res) => {
    const userId = req.params.id;
});

// 查询参数
app.get('/search', (req, res) => {
    const keyword = req.query.q;
});

// 路由分组
const userRouter = express.Router();
userRouter.get('/', listUsers);
userRouter.post('/', createUser);
app.use('/api/users', userRouter);

请求对象(req)常用属性:

属性说明
req.params路由参数
req.query查询字符串参数
req.body请求体(需要中间件解析)
req.headers请求头
req.cookiesCookie(需要 cookie-parser)
req.ip客户端 IP
req.path请求路径
req.method请求方法

响应对象(res)常用方法:

方法说明
res.send()发送响应
res.json()发送 JSON 响应
res.status()设置状态码
res.redirect()重定向
res.render()渲染模板
res.sendFile()发送文件
res.download()下载文件
res.cookie()设置 Cookie

Koa框架

Koa 是 Express 原班人马打造的下一代 Web 框架,更轻量、更现代,基于 async/await。

Koa vs Express:

特性ExpressKoa
异步处理回调/Promiseasync/await 原生支持
中间件模型线性洋葱模型
内置功能较多极简,需要插件
错误处理需要专门中间件try-catch 即可
体积较大更小
学习曲线较低稍高

洋葱模型:

graph TB subgraph "Koa 洋葱模型" A["请求进入"] --> B["中间件1 前置"] B --> C["中间件2 前置"] C --> D["中间件3 前置"] D --> E["核心处理"] E --> F["中间件3 后置"] F --> G["中间件2 后置"] G --> H["中间件1 后置"] H --> I["响应返回"] end style A fill:#e3f2fd,stroke:#333 style E fill:#c8e6c9,stroke:#333 style I fill:#ffccbc,stroke:#333

Koa 中间件示例:

app.use(async (ctx, next) => {
    console.log('1. 进入中间件1');
    await next();
    console.log('6. 离开中间件1');
});

app.use(async (ctx, next) => {
    console.log('2. 进入中间件2');
    await next();
    console.log('5. 离开中间件2');
});

app.use(async (ctx) => {
    console.log('3. 处理请求');
    ctx.body = 'Hello';
    console.log('4. 处理完成');
});

// 输出顺序:1 -> 2 -> 3 -> 4 -> 5 -> 6

Koa Context 对象:

属性/方法说明
ctx.requestKoa Request 对象
ctx.responseKoa Response 对象
ctx.reqNode.js 原生 request
ctx.resNode.js 原生 response
ctx.body响应体
ctx.status响应状态码
ctx.params路由参数(需要路由中间件)
ctx.query查询参数
ctx.throw()抛出 HTTP 错误

NestJS框架

NestJS 是一个企业级 Node.js 框架,深受 Angular 启发,使用 TypeScript 构建,提供完整的架构方案。

NestJS 核心特性:

特性说明
TypeScript 优先完整的类型支持
依赖注入IoC 容器,松耦合
模块化架构清晰的代码组织
装饰器声明式编程
内置支持WebSocket、GraphQL、微服务
测试友好内置测试工具

NestJS 架构:

graph TB subgraph "NestJS 架构" A["Module
模块"] --> B["Controller
控制器"] A --> C["Provider
提供者"] B --> D["Service
服务"] C --> D D --> E["Repository
仓库"] end F["HTTP 请求"] --> B B --> G["HTTP 响应"] style A fill:#e3f2fd,stroke:#333 style B fill:#c8e6c9,stroke:#333 style D fill:#fff9c4,stroke:#333

核心概念:

概念说明装饰器
Module组织代码的容器@Module()
Controller处理 HTTP 请求@Controller()
Provider可注入的服务@Injectable()
Middleware请求预处理实现 NestMiddleware
Guard权限验证@UseGuards()
Interceptor请求/响应拦截@UseInterceptors()
Pipe数据转换和验证@UsePipes()
Filter异常处理@Catch()

请求生命周期:

graph LR A["请求"] --> B["Middleware"] B --> C["Guard"] C --> D["Interceptor
前置"] D --> E["Pipe"] E --> F["Controller"] F --> G["Interceptor
后置"] G --> H["Filter
异常处理"] H --> I["响应"] style A fill:#e3f2fd,stroke:#333 style F fill:#c8e6c9,stroke:#333 style I fill:#ffccbc,stroke:#333

Fastify框架

Fastify 是一个高性能 Web 框架,专注于提供最佳的开发体验和最快的执行速度。

Fastify 特点:

特点说明
高性能比 Express 快约 2 倍
Schema 验证内置 JSON Schema 验证
插件系统强大的插件架构
TypeScript 支持良好的类型支持
日志集成内置 Pino 日志
钩子系统丰富的生命周期钩子

Fastify 生命周期钩子:

graph TB A["onRequest"] --> B["preParsing"] B --> C["preValidation"] C --> D["preHandler"] D --> E["Handler"] E --> F["preSerialization"] F --> G["onSend"] G --> H["onResponse"] style A fill:#e3f2fd,stroke:#333 style E fill:#c8e6c9,stroke:#333 style H fill:#ffccbc,stroke:#333

框架对比与选型

性能对比(请求/秒):

框架性能说明
Fastify~75,000最快
Koa~50,000较快
Express~35,000中等
NestJS~30,000功能丰富

选型建议:

graph TB A["项目需求"] --> B{"团队规模?"} B -->|小团队/个人| C{"性能要求?"} B -->|大团队/企业| D["NestJS"] C -->|高| E["Fastify"] C -->|一般| F{"学习曲线?"} F -->|低| G["Express"] F -->|可接受| H["Koa"] style D fill:#4caf50,stroke:#333,color:#fff style E fill:#2196f3,stroke:#333,color:#fff style G fill:#ff9800,stroke:#333 style H fill:#9c27b0,stroke:#333,color:#fff

框架选型总结:

场景推荐框架原因
快速原型Express简单、资料多
高性能 APIFastify性能最优
企业级应用NestJS架构完整、TypeScript
轻量级服务Koa现代、灵活
微服务NestJS/Fastify内置支持
学习入门Express生态最成熟

数据库操作

MySQL操作

Node.js 连接 MySQL 主要使用 mysql2 库,支持 Promise 和连接池。

连接方式对比:

方式说明适用场景
单连接每次创建新连接简单脚本
连接池复用连接生产环境
Promise 包装支持 async/await现代开发

连接池配置:

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'mydb',
    waitForConnections: true,
    connectionLimit: 10,      // 最大连接数
    queueLimit: 0,            // 等待队列限制
    enableKeepAlive: true,    // 保持连接
    keepAliveInitialDelay: 0
});

CRUD 操作:

// 查询
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [userId]);

// 插入
const [result] = await pool.query(
    'INSERT INTO users (name, email) VALUES (?, ?)',
    [name, email]
);
console.log('插入ID:', result.insertId);

// 更新
const [result] = await pool.query(
    'UPDATE users SET name = ? WHERE id = ?',
    [newName, userId]
);
console.log('影响行数:', result.affectedRows);

// 删除
const [result] = await pool.query('DELETE FROM users WHERE id = ?', [userId]);

事务处理:

const connection = await pool.getConnection();
try {
    await connection.beginTransaction();
    
    await connection.query('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]);
    await connection.query('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]);
    
    await connection.commit();
} catch (err) {
    await connection.rollback();
    throw err;
} finally {
    connection.release();
}

防止 SQL 注入:

方法说明
参数化查询使用 ? 占位符
escapemysql.escape(value)
escapeIdmysql.escapeId(identifier)

MongoDB操作

MongoDB 是最流行的 NoSQL 数据库,Node.js 使用 mongodb 官方驱动或 mongoose ODM。

MongoDB 驱动 vs Mongoose:

特性mongodb 驱动mongoose
类型原生驱动ODM
Schema
验证手动内置
中间件
性能更快略慢
学习曲线

Mongoose 基本使用:

const mongoose = require('mongoose');

// 连接数据库
await mongoose.connect('mongodb://localhost:27017/mydb');

// 定义 Schema
const userSchema = new mongoose.Schema({
    name: { type: String, required: true },
    email: { type: String, unique: true },
    age: { type: Number, min: 0 },
    createdAt: { type: Date, default: Date.now }
});

// 创建 Model
const User = mongoose.model('User', userSchema);

CRUD 操作:

// 创建
const user = await User.create({ name: 'John', email: '[email protected]' });

// 查询
const users = await User.find({ age: { $gte: 18 } });
const user = await User.findById(id);
const user = await User.findOne({ email: '[email protected]' });

// 更新
await User.updateOne({ _id: id }, { name: 'Jane' });
await User.findByIdAndUpdate(id, { name: 'Jane' }, { new: true });

// 删除
await User.deleteOne({ _id: id });
await User.findByIdAndDelete(id);

Mongoose 中间件:

// 保存前中间件
userSchema.pre('save', async function(next) {
    if (this.isModified('password')) {
        this.password = await bcrypt.hash(this.password, 10);
    }
    next();
});

// 查询后中间件
userSchema.post('find', function(docs) {
    console.log('查询到', docs.length, '条记录');
});

Redis操作

Redis 是高性能的内存数据库,常用于缓存、会话存储、消息队列

Node.js Redis 客户端:

说明
ioredis功能丰富,支持集群
redis官方库,v4 后支持 Promise

ioredis 基本操作:

const Redis = require('ioredis');
const redis = new Redis({
    host: 'localhost',
    port: 6379,
    password: 'password',
    db: 0
});

// 字符串操作
await redis.set('key', 'value');
await redis.set('key', 'value', 'EX', 3600);  // 设置过期时间
const value = await redis.get('key');

// 哈希操作
await redis.hset('user:1', 'name', 'John', 'age', '25');
const user = await redis.hgetall('user:1');

// 列表操作
await redis.lpush('queue', 'task1', 'task2');
const task = await redis.rpop('queue');

// 集合操作
await redis.sadd('tags', 'node', 'javascript');
const tags = await redis.smembers('tags');

// 有序集合
await redis.zadd('leaderboard', 100, 'player1', 200, 'player2');
const top = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');

Redis 常用场景:

场景数据结构说明
缓存String缓存数据库查询结果
会话存储String/Hash存储用户会话
计数器String访问量、点赞数
排行榜Sorted Set游戏排名
消息队列List任务队列
发布订阅Pub/Sub实时通知
分布式锁String + NX防止并发问题

分布式锁实现:

// 获取锁
async function acquireLock(key, ttl = 10000) {
    const lockKey = `lock:${key}`;
    const lockValue = Date.now().toString();
    const result = await redis.set(lockKey, lockValue, 'PX', ttl, 'NX');
    return result === 'OK' ? lockValue : null;
}

// 释放锁(使用 Lua 脚本保证原子性)
async function releaseLock(key, lockValue) {
    const script = `
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
    `;
    return await redis.eval(script, 1, `lock:${key}`, lockValue);
}

ORM框架使用

ORM(Object-Relational Mapping)将数据库表映射为对象,简化数据库操作。

主流 ORM 对比:

ORM数据库支持TypeScript特点
SequelizeMySQL, PostgreSQL, SQLite, MSSQL支持功能全面,社区大
TypeORM多种原生TypeScript 优先
Prisma多种原生现代、类型安全
Knex.js多种支持查询构建器

Prisma 使用示例:

// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}
// 使用 Prisma Client
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

// 创建
const user = await prisma.user.create({
    data: { email: '[email protected]', name: 'John' }
});

// 查询(包含关联)
const userWithPosts = await prisma.user.findUnique({
    where: { id: 1 },
    include: { posts: true }
});

// 事务
await prisma.$transaction([
    prisma.user.update({ where: { id: 1 }, data: { name: 'Jane' } }),
    prisma.post.create({ data: { title: 'Hello', authorId: 1 } })
]);

ORM 选型建议:

场景推荐原因
TypeScript 项目Prisma/TypeORM类型安全
快速开发Prisma开发体验好
复杂查询Knex.js灵活的查询构建
传统项目Sequelize成熟稳定

性能优化

内存管理与优化

Node.js 使用 V8 引擎的自动垃圾回收,但不当的代码仍可能导致内存泄漏

V8 内存限制:

系统默认限制说明
64位系统~1.4GB老生代约 1.4GB
32位系统~0.7GB老生代约 0.7GB

调整内存限制:

# 设置最大老生代内存为 4GB
node --max-old-space-size=4096 app.js

# 设置最大新生代内存
node --max-semi-space-size=64 app.js

常见内存泄漏场景:

场景原因解决方案
全局变量意外创建全局变量使用 strict 模式
闭包闭包持有大对象引用及时释放引用
事件监听器未移除的监听器使用 once 或手动移除
定时器未清除的定时器clearInterval/clearTimeout
缓存无限增长缓存没有过期策略使用 LRU 缓存
大数组/对象持续增长的数据结构定期清理

内存泄漏检测:

graph TB subgraph "内存泄漏检测流程" A["监控内存使用"] --> B{"内存持续增长?"} B -->|是| C["生成堆快照"] C --> D["对比快照"] D --> E["定位泄漏对象"] E --> F["分析引用链"] F --> G["修复代码"] B -->|否| H["正常"] end style A fill:#e3f2fd,stroke:#333 style C fill:#fff9c4,stroke:#333 style G fill:#c8e6c9,stroke:#333

内存监控代码:

// 获取内存使用情况
const used = process.memoryUsage();
console.log({
    rss: `${Math.round(used.rss / 1024 / 1024)} MB`,      // 常驻内存
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`,  // 堆总量
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`,    // 堆使用量
    external: `${Math.round(used.external / 1024 / 1024)} MB`     // C++ 对象
});

// 定期监控
setInterval(() => {
    const { heapUsed } = process.memoryUsage();
    if (heapUsed > 500 * 1024 * 1024) {  // 超过 500MB
        console.warn('内存使用过高');
    }
}, 30000);

内存优化技巧:

技巧说明
使用 Stream处理大文件避免一次性加载
及时释放引用不再使用的对象设为 null
使用 Buffer 池Buffer.allocUnsafe + 手动管理
限制缓存大小使用 LRU 缓存策略
避免大对象拆分大对象,分批处理
使用 WeakMap/WeakSet允许垃圾回收

CPU密集型任务处理

Node.js 单线程模型不适合 CPU 密集型任务,需要特殊处理。

CPU 密集型任务的问题:

graph LR subgraph "单线程阻塞" A["请求1"] --> B["CPU密集计算
阻塞10秒"] B --> C["响应1"] D["请求2"] -.->|等待| B E["请求3"] -.->|等待| B end style B fill:#f44336,stroke:#333,color:#fff

解决方案对比:

方案适用场景优点缺点
Worker Threads计算密集型共享内存,通信快复杂度高
Child Process独立任务隔离性好通信开销大
ClusterWeb 服务扩展简单不适合单任务
外部服务专业计算专业优化网络开销

Worker Threads 示例:

// main.js
const { Worker } = require('worker_threads');

function runWorker(data) {
    return new Promise((resolve, reject) => {
        const worker = new Worker('./worker.js', {
            workerData: data
        });
        worker.on('message', resolve);
        worker.on('error', reject);
        worker.on('exit', (code) => {
            if (code !== 0) reject(new Error(`Worker stopped with code ${code}`));
        });
    });
}

// worker.js
const { parentPort, workerData } = require('worker_threads');

// 执行 CPU 密集型计算
function heavyComputation(data) {
    // 复杂计算...
    return result;
}

const result = heavyComputation(workerData);
parentPort.postMessage(result);

Worker 线程池:

const { Worker } = require('worker_threads');
const os = require('os');

class WorkerPool {
    constructor(workerPath, poolSize = os.cpus().length) {
        this.workerPath = workerPath;
        this.poolSize = poolSize;
        this.workers = [];
        this.queue = [];
        this.init();
    }
    
    init() {
        for (let i = 0; i < this.poolSize; i++) {
            this.addWorker();
        }
    }
    
    addWorker() {
        const worker = new Worker(this.workerPath);
        worker.on('message', (result) => {
            worker.currentResolve(result);
            worker.busy = false;
            this.processQueue();
        });
        worker.busy = false;
        this.workers.push(worker);
    }
    
    runTask(data) {
        return new Promise((resolve, reject) => {
            const task = { data, resolve, reject };
            this.queue.push(task);
            this.processQueue();
        });
    }
    
    processQueue() {
        if (this.queue.length === 0) return;
        const worker = this.workers.find(w => !w.busy);
        if (!worker) return;
        
        const task = this.queue.shift();
        worker.busy = true;
        worker.currentResolve = task.resolve;
        worker.postMessage(task.data);
    }
}

集群与负载均衡

使用 cluster 模块可以充分利用多核 CPU。

Cluster 工作原理:

graph TB subgraph "Cluster 架构" A["Master 进程"] --> B["Worker 1"] A --> C["Worker 2"] A --> D["Worker 3"] A --> E["Worker 4"] end F["客户端请求"] --> A A -->|轮询分发| B A -->|轮询分发| C A -->|轮询分发| D A -->|轮询分发| E style A fill:#ff9800,stroke:#333 style B fill:#4caf50,stroke:#333,color:#fff style C fill:#4caf50,stroke:#333,color:#fff style D fill:#4caf50,stroke:#333,color:#fff style E fill:#4caf50,stroke:#333,color:#fff

Cluster 基本使用:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log(`Master ${process.pid} is running`);
    
    // Fork workers
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
    
    cluster.on('exit', (worker, code, signal) => {
        console.log(`Worker ${worker.process.pid} died`);
        cluster.fork();  // 重启 worker
    });
} else {
    http.createServer((req, res) => {
        res.writeHead(200);
        res.end('Hello World\n');
    }).listen(8000);
    
    console.log(`Worker ${process.pid} started`);
}

负载均衡策略:

策略说明适用场景
轮询(Round-Robin)默认策略,依次分发通用场景
最少连接分发给连接数最少的长连接场景
IP Hash同一 IP 分发到同一 Worker会话保持

性能监控与分析

性能指标:

指标说明关注点
响应时间请求处理耗时P50, P95, P99
吞吐量每秒处理请求数QPS/TPS
错误率错误请求占比5xx 错误
CPU 使用率CPU 占用情况是否过高
内存使用内存占用情况是否泄漏
事件循环延迟事件循环阻塞时间是否阻塞

事件循环监控:

// 监控事件循环延迟
const start = process.hrtime();
setImmediate(() => {
    const delta = process.hrtime(start);
    const nanosec = delta[0] * 1e9 + delta[1];
    const ms = nanosec / 1e6;
    if (ms > 100) {
        console.warn(`事件循环延迟: ${ms}ms`);
    }
});

// 使用 perf_hooks
const { monitorEventLoopDelay } = require('perf_hooks');
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();

setInterval(() => {
    console.log({
        min: h.min / 1e6,
        max: h.max / 1e6,
        mean: h.mean / 1e6,
        p99: h.percentile(99) / 1e6
    });
}, 5000);

性能分析工具:

工具用途说明
–inspectChrome DevTools 调试内置
clinic.js性能诊断套件推荐
0x火焰图生成CPU 分析
heapdump堆快照内存分析
v8-profilerV8 性能分析详细分析

使用 Chrome DevTools:

# 启动调试模式
node --inspect app.js

# 打开 Chrome 访问
chrome://inspect

性能优化检查清单:

检查项说明
✅ 使用连接池数据库、Redis 连接复用
✅ 启用 Gzip压缩响应数据
✅ 使用缓存Redis 缓存热点数据
✅ 异步操作避免同步阻塞
✅ Stream 处理大文件流式处理
✅ 集群部署利用多核 CPU
✅ 负载均衡Nginx 反向代理
✅ 监控告警及时发现问题

安全实践

常见安全漏洞

OWASP Top 10 在 Node.js 中的体现:

漏洞类型说明Node.js 风险点
注入攻击SQL/NoSQL/命令注入数据库查询、exec
身份认证失效弱密码、会话管理JWT、Session
敏感数据泄露数据未加密日志、响应
XXEXML 外部实体XML 解析
访问控制失效越权访问API 权限
安全配置错误默认配置、调试模式生产环境配置
XSS跨站脚本模板渲染
不安全的反序列化恶意对象JSON.parse
使用含漏洞的组件依赖漏洞npm 包
日志和监控不足无法追踪攻击日志记录

SQL 注入防护:

// ❌ 危险:字符串拼接
const query = `SELECT * FROM users WHERE id = ${userId}`;

// ✅ 安全:参数化查询
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [userId]);

// ✅ 安全:使用 ORM
const user = await User.findByPk(userId);

NoSQL 注入防护:

// ❌ 危险:直接使用用户输入
const user = await User.findOne({ username: req.body.username });
// 攻击者可以传入 { "$gt": "" } 绕过验证

// ✅ 安全:类型验证
const username = String(req.body.username);
const user = await User.findOne({ username });

// ✅ 安全:使用 Schema 验证
const schema = Joi.object({
    username: Joi.string().alphanum().min(3).max(30).required()
});

命令注入防护:

// ❌ 危险:直接拼接命令
const { exec } = require('child_process');
exec(`ls ${userInput}`);  // 用户可输入 "; rm -rf /"

// ✅ 安全:使用 execFile 或参数数组
const { execFile } = require('child_process');
execFile('ls', [userInput]);

// ✅ 安全:使用 spawn 并禁用 shell
const { spawn } = require('child_process');
spawn('ls', [userInput], { shell: false });

XSS 防护:

// ❌ 危险:直接输出用户内容
res.send(`<div>${userInput}</div>`);

// ✅ 安全:转义 HTML
const escapeHtml = require('escape-html');
res.send(`<div>${escapeHtml(userInput)}</div>`);

// ✅ 安全:使用模板引擎自动转义
// EJS 默认转义
<%= userInput %>

// ✅ 安全:设置 CSP 头
app.use(helmet.contentSecurityPolicy({
    directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'"]
    }
}));

安全编码规范

安全中间件 - Helmet:

const helmet = require('helmet');
app.use(helmet());

// Helmet 包含的安全头
// X-DNS-Prefetch-Control
// X-Frame-Options
// X-Content-Type-Options
// X-XSS-Protection
// Strict-Transport-Security
// Content-Security-Policy

输入验证:

const Joi = require('joi');

const userSchema = Joi.object({
    username: Joi.string().alphanum().min(3).max(30).required(),
    email: Joi.string().email().required(),
    password: Joi.string().pattern(/^[a-zA-Z0-9]{8,30}$/).required(),
    age: Joi.number().integer().min(0).max(150)
});

// 验证
const { error, value } = userSchema.validate(req.body);
if (error) {
    return res.status(400).json({ error: error.details[0].message });
}

速率限制:

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000,  // 15 分钟
    max: 100,                   // 最多 100 次请求
    message: '请求过于频繁,请稍后再试',
    standardHeaders: true,
    legacyHeaders: false
});

app.use('/api/', limiter);

// 登录接口更严格的限制
const loginLimiter = rateLimit({
    windowMs: 60 * 60 * 1000,  // 1 小时
    max: 5,                     // 最多 5 次
    message: '登录尝试次数过多'
});
app.use('/api/login', loginLimiter);

安全配置检查清单:

检查项说明
✅ 使用 HTTPS加密传输
✅ 设置安全头使用 Helmet
✅ 输入验证验证所有用户输入
✅ 参数化查询防止注入
✅ 速率限制防止暴力攻击
✅ 错误处理不泄露敏感信息
✅ 依赖审计npm audit
✅ 环境变量敏感配置不硬编码
✅ 日志脱敏不记录敏感数据
✅ CORS 配置限制跨域访问

认证与授权

认证方式对比:

方式优点缺点适用场景
Session服务端控制,可即时失效需要存储,扩展性差传统 Web
JWT无状态,易扩展无法即时失效API、微服务
OAuth 2.0第三方授权复杂第三方登录

JWT 实现:

const jwt = require('jsonwebtoken');

// 生成 Token
function generateToken(user) {
    return jwt.sign(
        { id: user.id, role: user.role },
        process.env.JWT_SECRET,
        { expiresIn: '24h' }
    );
}

// 验证中间件
function authMiddleware(req, res, next) {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) {
        return res.status(401).json({ error: '未提供 Token' });
    }
    
    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        next();
    } catch (err) {
        return res.status(401).json({ error: 'Token 无效' });
    }
}

JWT 安全最佳实践:

实践说明
使用强密钥至少 256 位随机字符串
设置过期时间不要设置过长
使用 HTTPS防止 Token 被截获
不存储敏感信息Payload 可被解码
实现 Token 刷新短期 Token + 刷新机制
黑名单机制支持 Token 失效

RBAC 权限控制:

// 角色定义
const roles = {
    admin: ['read', 'write', 'delete', 'manage'],
    editor: ['read', 'write'],
    viewer: ['read']
};

// 权限检查中间件
function checkPermission(permission) {
    return (req, res, next) => {
        const userRole = req.user.role;
        const permissions = roles[userRole] || [];
        
        if (!permissions.includes(permission)) {
            return res.status(403).json({ error: '权限不足' });
        }
        next();
    };
}

// 使用
app.delete('/api/posts/:id', 
    authMiddleware, 
    checkPermission('delete'), 
    deletePost
);

密码安全:

const bcrypt = require('bcrypt');

// 密码加密
async function hashPassword(password) {
    const saltRounds = 12;  // 推荐 10-12
    return await bcrypt.hash(password, saltRounds);
}

// 密码验证
async function verifyPassword(password, hash) {
    return await bcrypt.compare(password, hash);
}

// 密码强度验证
function validatePassword(password) {
    const minLength = 8;
    const hasUpperCase = /[A-Z]/.test(password);
    const hasLowerCase = /[a-z]/.test(password);
    const hasNumbers = /\d/.test(password);
    const hasSpecialChar = /[!@#$%^&*]/.test(password);
    
    return password.length >= minLength && 
           hasUpperCase && hasLowerCase && 
           hasNumbers && hasSpecialChar;
}

部署与运维

PM2进程管理

PM2 是 Node.js 应用的生产级进程管理器,提供进程守护、负载均衡、日志管理等功能。

PM2 核心功能:

功能说明
进程守护应用崩溃自动重启
负载均衡集群模式,利用多核
零停机重启平滑重启,不中断服务
日志管理集中管理应用日志
监控内存、CPU 监控
启动脚本开机自启动

常用命令:

# 启动应用
pm2 start app.js
pm2 start app.js --name "my-app"
pm2 start app.js -i max  # 集群模式,使用所有 CPU

# 管理应用
pm2 list                 # 查看所有应用
pm2 stop <app>           # 停止应用
pm2 restart <app>        # 重启应用
pm2 reload <app>         # 零停机重启
pm2 delete <app>         # 删除应用

# 日志
pm2 logs                 # 查看所有日志
pm2 logs <app>           # 查看指定应用日志
pm2 flush                # 清空日志

# 监控
pm2 monit                # 实时监控面板
pm2 show <app>           # 查看应用详情

# 启动脚本
pm2 startup              # 生成启动脚本
pm2 save                 # 保存当前进程列表

ecosystem.config.js 配置:

module.exports = {
    apps: [{
        name: 'my-app',
        script: './app.js',
        instances: 'max',           // 集群实例数
        exec_mode: 'cluster',       // 集群模式
        watch: false,               // 生产环境关闭
        max_memory_restart: '1G',   // 内存超限重启
        env: {
            NODE_ENV: 'development'
        },
        env_production: {
            NODE_ENV: 'production',
            PORT: 3000
        },
        // 日志配置
        log_date_format: 'YYYY-MM-DD HH:mm:ss',
        error_file: './logs/error.log',
        out_file: './logs/out.log',
        merge_logs: true,
        // 重启策略
        exp_backoff_restart_delay: 100,
        max_restarts: 10,
        min_uptime: '10s'
    }]
};

使用配置文件:

# 启动
pm2 start ecosystem.config.js --env production

# 重启
pm2 reload ecosystem.config.js --env production

Docker容器化部署

Dockerfile 最佳实践:

# 使用官方 Node.js 镜像
FROM node:18-alpine AS builder

# 设置工作目录
WORKDIR /app

# 复制 package 文件
COPY package*.json ./

# 安装依赖(仅生产依赖)
RUN npm ci --only=production

# 复制源代码
COPY . .

# 构建(如果需要)
RUN npm run build

# 生产镜像
FROM node:18-alpine

WORKDIR /app

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

# 复制构建产物
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./

# 切换用户
USER nodejs

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD node healthcheck.js || exit 1

# 启动命令
CMD ["node", "dist/app.js"]

docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=mongodb://mongo:27017/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      - mongo
      - redis
    restart: unless-stopped
    deploy:
      replicas: 2
      resources:
        limits:
          memory: 512M
          cpus: '0.5'

  mongo:
    image: mongo:6
    volumes:
      - mongo_data:/data/db
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    restart: unless-stopped

volumes:
  mongo_data:

Docker 优化技巧:

技巧说明
多阶段构建减小镜像体积
使用 Alpine更小的基础镜像
npm ci更快、更可靠的安装
非 root 用户安全最佳实践
健康检查容器健康监控
.dockerignore排除不需要的文件

日志管理

日志级别:

级别说明使用场景
error错误需要立即处理的问题
warn警告潜在问题
info信息重要业务事件
debug调试开发调试信息
trace追踪详细追踪信息

Winston 日志库:

const winston = require('winston');

const logger = winston.createLogger({
    level: process.env.LOG_LEVEL || 'info',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.errors({ stack: true }),
        winston.format.json()
    ),
    defaultMeta: { service: 'my-app' },
    transports: [
        // 错误日志
        new winston.transports.File({ 
            filename: 'logs/error.log', 
            level: 'error' 
        }),
        // 所有日志
        new winston.transports.File({ 
            filename: 'logs/combined.log' 
        })
    ]
});

// 开发环境输出到控制台
if (process.env.NODE_ENV !== 'production') {
    logger.add(new winston.transports.Console({
        format: winston.format.combine(
            winston.format.colorize(),
            winston.format.simple()
        )
    }));
}

// 使用
logger.info('用户登录', { userId: 123, ip: '192.168.1.1' });
logger.error('数据库连接失败', { error: err.message });

日志最佳实践:

实践说明
结构化日志使用 JSON 格式
添加上下文请求 ID、用户 ID
日志轮转按大小或时间切割
敏感信息脱敏不记录密码、Token
集中收集ELK、Loki 等
合理级别生产环境用 info

请求日志中间件:

const morgan = require('morgan');

// 自定义格式
morgan.token('request-id', (req) => req.id);

app.use(morgan(':request-id :method :url :status :response-time ms'));

// 生产环境使用 combined 格式
if (process.env.NODE_ENV === 'production') {
    app.use(morgan('combined', {
        stream: { write: (msg) => logger.info(msg.trim()) }
    }));
}

监控与告警

监控指标体系:

graph TB subgraph "监控层次" A["基础设施监控
CPU、内存、磁盘、网络"] B["应用监控
响应时间、错误率、QPS"] C["业务监控
订单量、转化率"] end A --> D["告警系统"] B --> D C --> D D --> E["通知渠道
邮件、短信、钉钉"] style A fill:#e3f2fd,stroke:#333 style B fill:#c8e6c9,stroke:#333 style C fill:#fff9c4,stroke:#333 style D fill:#ffccbc,stroke:#333

Prometheus 指标暴露:

const promClient = require('prom-client');

// 启用默认指标
promClient.collectDefaultMetrics();

// 自定义指标
const httpRequestDuration = new promClient.Histogram({
    name: 'http_request_duration_seconds',
    help: 'HTTP 请求耗时',
    labelNames: ['method', 'route', 'status'],
    buckets: [0.1, 0.5, 1, 2, 5]
});

const httpRequestTotal = new promClient.Counter({
    name: 'http_requests_total',
    help: 'HTTP 请求总数',
    labelNames: ['method', 'route', 'status']
});

// 中间件记录指标
app.use((req, res, next) => {
    const start = Date.now();
    res.on('finish', () => {
        const duration = (Date.now() - start) / 1000;
        const labels = {
            method: req.method,
            route: req.route?.path || req.path,
            status: res.statusCode
        };
        httpRequestDuration.observe(labels, duration);
        httpRequestTotal.inc(labels);
    });
    next();
});

// 暴露指标端点
app.get('/metrics', async (req, res) => {
    res.set('Content-Type', promClient.register.contentType);
    res.end(await promClient.register.metrics());
});

健康检查端点:

app.get('/health', async (req, res) => {
    const health = {
        status: 'ok',
        timestamp: new Date().toISOString(),
        uptime: process.uptime(),
        checks: {}
    };
    
    // 检查数据库
    try {
        await db.query('SELECT 1');
        health.checks.database = 'ok';
    } catch (err) {
        health.checks.database = 'error';
        health.status = 'degraded';
    }
    
    // 检查 Redis
    try {
        await redis.ping();
        health.checks.redis = 'ok';
    } catch (err) {
        health.checks.redis = 'error';
        health.status = 'degraded';
    }
    
    const statusCode = health.status === 'ok' ? 200 : 503;
    res.status(statusCode).json(health);
});

告警规则示例(Prometheus):

groups:
  - name: node-app
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "高错误率告警"
          description: "错误率超过 5%"
      
      - alert: HighResponseTime
        expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "响应时间过长"
          description: "P95 响应时间超过 2 秒"

实战案例

RESTful API开发

RESTful 设计原则:

原则说明
资源导向URL 表示资源,不是动作
HTTP 方法语义GET 查询、POST 创建、PUT 更新、DELETE 删除
无状态每个请求包含所有必要信息
统一接口一致的 URL 和响应格式
分层系统客户端不知道是否直连服务器

URL 设计规范:

操作HTTP 方法URL说明
获取列表GET/api/users获取用户列表
获取单个GET/api/users/:id获取指定用户
创建POST/api/users创建用户
全量更新PUT/api/users/:id更新用户
部分更新PATCH/api/users/:id部分更新
删除DELETE/api/users/:id删除用户
子资源GET/api/users/:id/orders用户的订单

项目结构:

src/
├── app.js              # 应用入口
├── config/             # 配置文件
│   └── index.js
├── controllers/        # 控制器
│   └── userController.js
├── middlewares/        # 中间件
│   ├── auth.js
│   ├── errorHandler.js
│   └── validator.js
├── models/             # 数据模型
│   └── User.js
├── routes/             # 路由
│   ├── index.js
│   └── userRoutes.js
├── services/           # 业务逻辑
│   └── userService.js
├── utils/              # 工具函数
│   └── response.js
└── validators/         # 验证规则
    └── userValidator.js

统一响应格式:

// utils/response.js
class ApiResponse {
    static success(res, data, message = 'Success', statusCode = 200) {
        return res.status(statusCode).json({
            success: true,
            message,
            data
        });
    }
    
    static error(res, message, statusCode = 500, errors = null) {
        return res.status(statusCode).json({
            success: false,
            message,
            errors
        });
    }
    
    static paginated(res, data, pagination) {
        return res.status(200).json({
            success: true,
            data,
            pagination: {
                page: pagination.page,
                limit: pagination.limit,
                total: pagination.total,
                totalPages: Math.ceil(pagination.total / pagination.limit)
            }
        });
    }
}

错误处理中间件:

// middlewares/errorHandler.js
class AppError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.statusCode = statusCode;
        this.isOperational = true;
    }
}

const errorHandler = (err, req, res, next) => {
    err.statusCode = err.statusCode || 500;
    
    if (process.env.NODE_ENV === 'development') {
        return res.status(err.statusCode).json({
            success: false,
            message: err.message,
            stack: err.stack
        });
    }
    
    // 生产环境
    if (err.isOperational) {
        return res.status(err.statusCode).json({
            success: false,
            message: err.message
        });
    }
    
    // 未知错误
    console.error('ERROR:', err);
    return res.status(500).json({
        success: false,
        message: '服务器内部错误'
    });
};

分页查询实现:

// services/userService.js
async function getUsers(query) {
    const page = parseInt(query.page) || 1;
    const limit = parseInt(query.limit) || 10;
    const skip = (page - 1) * limit;
    
    const filter = {};
    if (query.search) {
        filter.$or = [
            { name: new RegExp(query.search, 'i') },
            { email: new RegExp(query.search, 'i') }
        ];
    }
    
    const [users, total] = await Promise.all([
        User.find(filter).skip(skip).limit(limit).sort({ createdAt: -1 }),
        User.countDocuments(filter)
    ]);
    
    return { users, pagination: { page, limit, total } };
}

WebSocket实时通信

WebSocket vs HTTP:

特性HTTPWebSocket
连接方式短连接长连接
通信方向单向(请求-响应)双向
实时性轮询实现原生支持
开销每次请求带头部握手后开销小
适用场景普通 API实时应用

Socket.IO 实现:

const { Server } = require('socket.io');
const http = require('http');
const express = require('express');

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
    cors: {
        origin: '*',
        methods: ['GET', 'POST']
    }
});

// 连接处理
io.on('connection', (socket) => {
    console.log('用户连接:', socket.id);
    
    // 加入房间
    socket.on('join-room', (roomId) => {
        socket.join(roomId);
        socket.to(roomId).emit('user-joined', { userId: socket.id });
    });
    
    // 发送消息
    socket.on('send-message', (data) => {
        io.to(data.roomId).emit('new-message', {
            userId: socket.id,
            message: data.message,
            timestamp: new Date()
        });
    });
    
    // 断开连接
    socket.on('disconnect', () => {
        console.log('用户断开:', socket.id);
    });
});

// 认证中间件
io.use((socket, next) => {
    const token = socket.handshake.auth.token;
    try {
        const user = verifyToken(token);
        socket.user = user;
        next();
    } catch (err) {
        next(new Error('认证失败'));
    }
});

WebSocket 架构:

graph TB subgraph "客户端" A["浏览器1"] B["浏览器2"] C["移动App"] end subgraph "服务端" D["Socket.IO Server"] E["Redis Adapter"] end subgraph "多实例部署" F["Server 1"] G["Server 2"] end A --> D B --> D C --> D D --> E E --> F E --> G style D fill:#4caf50,stroke:#333,color:#fff style E fill:#f44336,stroke:#333,color:#fff

Redis Adapter(多实例支持):

const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');

const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();

Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
    io.adapter(createAdapter(pubClient, subClient));
});

微服务架构实践

微服务通信方式:

方式说明适用场景
HTTP/REST同步调用简单查询
gRPC高性能 RPC内部服务通信
消息队列异步通信解耦、削峰
事件驱动发布订阅事件通知

微服务架构图:

graph TB subgraph "API Gateway" A["Gateway
Node.js"] end subgraph "微服务" B["用户服务"] C["订单服务"] D["商品服务"] E["支付服务"] end subgraph "基础设施" F["服务注册
Consul"] G["消息队列
RabbitMQ"] H["配置中心"] end A --> B A --> C A --> D A --> E B --> F C --> F D --> F E --> F B --> G C --> G E --> G style A fill:#ff9800,stroke:#333 style F fill:#4caf50,stroke:#333,color:#fff style G fill:#2196f3,stroke:#333,color:#fff

消息队列集成(RabbitMQ):

const amqp = require('amqplib');

class MessageQueue {
    async connect() {
        this.connection = await amqp.connect('amqp://localhost');
        this.channel = await this.connection.createChannel();
    }
    
    // 发布消息
    async publish(exchange, routingKey, message) {
        await this.channel.assertExchange(exchange, 'topic', { durable: true });
        this.channel.publish(
            exchange,
            routingKey,
            Buffer.from(JSON.stringify(message)),
            { persistent: true }
        );
    }
    
    // 订阅消息
    async subscribe(exchange, queue, routingKey, handler) {
        await this.channel.assertExchange(exchange, 'topic', { durable: true });
        await this.channel.assertQueue(queue, { durable: true });
        await this.channel.bindQueue(queue, exchange, routingKey);
        
        this.channel.consume(queue, async (msg) => {
            try {
                const content = JSON.parse(msg.content.toString());
                await handler(content);
                this.channel.ack(msg);
            } catch (err) {
                this.channel.nack(msg, false, false);
            }
        });
    }
}

// 使用示例
const mq = new MessageQueue();
await mq.connect();

// 发布订单创建事件
await mq.publish('orders', 'order.created', {
    orderId: '123',
    userId: '456',
    amount: 100
});

// 订阅订单事件
await mq.subscribe('orders', 'payment-service', 'order.*', async (message) => {
    console.log('收到订单事件:', message);
    // 处理支付逻辑
});

服务发现(Consul):

const Consul = require('consul');
const consul = new Consul();

// 注册服务
async function registerService() {
    await consul.agent.service.register({
        name: 'user-service',
        id: `user-service-${process.env.PORT}`,
        address: 'localhost',
        port: parseInt(process.env.PORT),
        check: {
            http: `http://localhost:${process.env.PORT}/health`,
            interval: '10s'
        }
    });
}

// 发现服务
async function discoverService(serviceName) {
    const services = await consul.health.service({
        service: serviceName,
        passing: true
    });
    
    if (services.length === 0) {
        throw new Error(`服务 ${serviceName} 不可用`);
    }
    
    // 简单负载均衡
    const index = Math.floor(Math.random() * services.length);
    const service = services[index].Service;
    return `http://${service.Address}:${service.Port}`;
}

分布式追踪:

const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');

const provider = new NodeTracerProvider();
provider.addSpanProcessor(
    new SimpleSpanProcessor(
        new JaegerExporter({
            endpoint: 'http://localhost:14268/api/traces'
        })
    )
);
provider.register();

const tracer = provider.getTracer('my-service');

// 创建 Span
const span = tracer.startSpan('operation-name');
try {
    // 业务逻辑
    span.setAttribute('user.id', userId);
} finally {
    span.end();
}

常见问题与排查

内存泄漏排查

内存泄漏症状:

症状说明
内存持续增长重启后恢复,运行一段时间后又增长
响应变慢GC 频繁导致性能下降
OOM 崩溃内存超限被系统杀死

排查步骤:

graph TB A["发现内存问题"] --> B["监控内存趋势"] B --> C["生成堆快照"] C --> D["对比多个快照"] D --> E["找出增长的对象"] E --> F["分析引用链"] F --> G["定位问题代码"] G --> H["修复并验证"] style A fill:#f44336,stroke:#333,color:#fff style C fill:#fff9c4,stroke:#333 style G fill:#4caf50,stroke:#333,color:#fff

生成堆快照:

// 方式1:使用 v8 模块
const v8 = require('v8');
const fs = require('fs');

function takeHeapSnapshot() {
    const snapshotStream = v8.writeHeapSnapshot();
    console.log('堆快照已保存:', snapshotStream);
}

// 方式2:使用 heapdump 模块
const heapdump = require('heapdump');
heapdump.writeSnapshot('./heap-' + Date.now() + '.heapsnapshot');

// 方式3:通过信号触发
process.on('SIGUSR2', () => {
    heapdump.writeSnapshot();
});

使用 Chrome DevTools 分析:

  1. 启动应用:node --inspect app.js
  2. 打开 Chrome:chrome://inspect
  3. 点击 “inspect” 连接到应用
  4. 在 Memory 标签页生成快照
  5. 对比多个快照,找出增长的对象

常见内存泄漏模式:

模式示例解决方案
全局变量累积global.cache.push(data)使用 LRU 缓存
闭包引用闭包持有大对象及时释放引用
事件监听器重复添加监听器使用 once 或移除
定时器未清除setInterval 未清除clearInterval
Promise 未处理大量 pending Promise添加错误处理

内存泄漏修复示例:

// ❌ 泄漏:闭包持有大对象
function processData() {
    const largeData = loadLargeData();
    return function() {
        // largeData 永远不会被释放
        console.log(largeData.length);
    };
}

// ✅ 修复:只保留需要的数据
function processData() {
    const largeData = loadLargeData();
    const length = largeData.length;  // 只保留需要的
    return function() {
        console.log(length);
    };
}

// ❌ 泄漏:事件监听器累积
class MyClass {
    constructor() {
        emitter.on('event', this.handler);  // 每次创建都添加
    }
}

// ✅ 修复:移除监听器
class MyClass {
    constructor() {
        this.handler = this.handler.bind(this);
        emitter.on('event', this.handler);
    }
    
    destroy() {
        emitter.off('event', this.handler);
    }
}

性能瓶颈定位

性能分析工具:

工具用途使用方式
–profV8 性能分析node --prof app.js
clinic.js综合诊断clinic doctor -- node app.js
0x火焰图0x app.js
autocannon压力测试autocannon http://localhost:3000

使用 clinic.js:

# 安装
npm install -g clinic

# 诊断
clinic doctor -- node app.js

# 火焰图
clinic flame -- node app.js

# 事件循环分析
clinic bubbleprof -- node app.js

火焰图分析:

graph TB subgraph "火焰图解读" A["宽度 = 时间占比"] B["高度 = 调用栈深度"] C["颜色 = 函数类型"] end D["找出宽的函数"] --> E["这是性能瓶颈"] F["找出深的调用栈"] --> G["可能需要优化"] style D fill:#f44336,stroke:#333,color:#fff style E fill:#ff9800,stroke:#333

常见性能问题:

问题症状解决方案
同步 I/O事件循环阻塞改用异步 API
CPU 密集计算响应延迟Worker Threads
大量小对象GC 频繁对象池
正则回溯CPU 100%优化正则表达式
JSON 序列化大对象慢流式处理
数据库查询响应慢索引、缓存

事件循环延迟监控:

const { monitorEventLoopDelay } = require('perf_hooks');

const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();

setInterval(() => {
    const stats = {
        min: histogram.min / 1e6,
        max: histogram.max / 1e6,
        mean: histogram.mean / 1e6,
        p99: histogram.percentile(99) / 1e6
    };
    
    if (stats.p99 > 100) {
        console.warn('事件循环延迟过高:', stats);
    }
    
    histogram.reset();
}, 5000);

常见错误处理

常见错误类型及解决:

错误原因解决方案
ECONNREFUSED连接被拒绝检查服务是否启动
ECONNRESET连接被重置检查网络、超时设置
ETIMEDOUT连接超时增加超时时间、检查网络
EADDRINUSE端口被占用更换端口或杀死占用进程
ENOMEM内存不足增加内存限制、优化代码
EMFILE文件描述符耗尽增加 ulimit、关闭未用连接

端口占用排查:

# 查找占用端口的进程
lsof -i :3000

# 杀死进程
kill -9 <PID>

# 或者使用 fuser
fuser -k 3000/tcp

文件描述符问题:

# 查看当前限制
ulimit -n

# 临时增加限制
ulimit -n 65535

# 永久修改(/etc/security/limits.conf)
* soft nofile 65535
* hard nofile 65535

未捕获异常处理:

// 捕获未处理的异常
process.on('uncaughtException', (err) => {
    console.error('未捕获的异常:', err);
    // 记录日志
    logger.error('uncaughtException', { error: err.message, stack: err.stack });
    // 优雅退出
    gracefulShutdown();
});

// 捕获未处理的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
    console.error('未处理的 Promise 拒绝:', reason);
    logger.error('unhandledRejection', { reason });
});

// 优雅退出
async function gracefulShutdown() {
    console.log('开始优雅退出...');
    
    // 停止接收新请求
    server.close();
    
    // 等待现有请求完成
    await new Promise(resolve => setTimeout(resolve, 5000));
    
    // 关闭数据库连接
    await db.close();
    
    // 关闭 Redis 连接
    await redis.quit();
    
    console.log('退出完成');
    process.exit(1);
}

// 监听退出信号
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);

调试技巧:

技巧说明
DEBUG 环境变量DEBUG=app:* node app.js
–inspectChrome DevTools 调试
console.trace()打印调用栈
Error.captureStackTrace自定义错误栈
source-map-supportTypeScript 源码映射

生产环境调试:

// 远程调试(谨慎使用)
// node --inspect=0.0.0.0:9229 app.js

// 按需开启调试
process.on('SIGUSR1', () => {
    const inspector = require('inspector');
    inspector.open(9229, '0.0.0.0');
    console.log('调试器已开启');
    
    // 30 秒后自动关闭
    setTimeout(() => {
        inspector.close();
        console.log('调试器已关闭');
    }, 30000);
});

错误追踪服务集成:

// Sentry 集成示例
const Sentry = require('@sentry/node');

Sentry.init({
    dsn: 'https://[email protected]/xxx',
    environment: process.env.NODE_ENV,
    tracesSampleRate: 0.1
});

// Express 集成
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());

// 路由...

app.use(Sentry.Handlers.errorHandler());

// 手动捕获错误
try {
    riskyOperation();
} catch (err) {
    Sentry.captureException(err);
    throw err;
}

Playwright 自动化测试

Playwright 是微软开源的新一代自动化测试工具,支持 Chromium、Firefox 和 WebKit 浏览器,适用于端到端测试(E2E)。

核心特性

  • 跨浏览器支持:一次编写,在所有现代浏览器中运行。
  • 跨平台:支持 Windows、Linux、macOS。
  • 多语言支持:支持 JavaScript/TypeScript、Python、Java、.NET。
  • 自动等待:自动等待元素准备就绪,减少 flaky tests。
  • 强大的工具链:包含代码生成器(Codegen)、调试器(Trace Viewer)。

安装与初始化

# 初始化项目
npm init playwright@latest

# 按提示选择:
# 1. TypeScript or JavaScript
# 2. Name of Tests folder (default: tests)
# 3. Add GitHub Actions workflow? (y/N)
# 4. Install Playwright browsers (can be done manually via 'npx playwright install')

基础使用

编写第一个测试 (tests/example.spec.ts):

import { test, expect } from '@playwright/test';

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // 验证页面标题
  await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // 点击 Get started 链接
  await page.getByRole('link', { name: 'Get started' }).click();

  // 验证 URL 包含 intro
  await expect(page).toHaveURL(/.*intro/);
});

常用操作

常用定位器 (Locators):

// 推荐使用面向用户的定位器
page.getByRole('button', { name: 'Submit' })
page.getByLabel('User Name')
page.getByPlaceholder('Enter your password')
page.getByText('Welcome')
page.getByTestId('submit-btn') // 需要在元素上添加 data-testid="submit-btn"

// CSS 选择器
page.locator('css=button.submit')
page.locator('#submit-btn')

常用断言 (Assertions):

await expect(locator).toBeVisible();
await expect(locator).toBeEnabled();
await expect(locator).toHaveText('Welcome');
await expect(locator).toHaveValue('123');
await expect(page).toHaveURL(/login/);

运行测试

# 运行所有测试
npx playwright test

# 运行特定测试文件
npx playwright test tests/example.spec.ts

# 带界面运行(UI 模式)
npx playwright test --ui

# 调试模式
npx playwright test --debug

# 查看测试报告
npx playwright show-report

常用技巧

录制测试脚本 (Codegen):

Playwright 提供了代码生成器,可以自动记录浏览器操作并生成测试代码。

# 启动录制并打开指定页面
npx playwright codegen playwright.dev

# 指定浏览器渠道(如使用本机安装的 Chrome)
npx playwright codegen --channel=chrome playwright.dev

# 将录制的代码保存到指定文件
npx playwright codegen -o tests/my-test.spec.ts playwright.dev

# 指定浏览器用户目录
npx playwright codegen \
  --channel=chrome \
  --user-data-dir="C:\Users\ralph\AppData\Local\Chrome-Automation"

Trace Viewer (调用栈查看):

配置 playwright.config.ts 开启 trace,可以在测试失败时查看完整的执行轨迹。

use: {
  trace: 'on-first-retry', // 首次重试时记录
},

总结

Node.js 作为一个强大的服务端 JavaScript 运行时,具有以下核心优势:

优势说明
高并发事件驱动、非阻塞 I/O
统一技术栈前后端使用同一语言
丰富生态npm 拥有海量包
快速开发轻量、灵活、迭代快
实时应用WebSocket 支持好

适用场景:

  • I/O 密集型应用
  • 实时通信应用
  • API 服务
  • 微服务架构
  • 工具开发

不适用场景:

  • CPU 密集型计算(可用 Worker Threads 缓解)
  • 需要强类型的大型项目(可用 TypeScript)

掌握 Node.js 的核心概念(事件循环、异步编程、Stream)和最佳实践(错误处理、性能优化、安全),能够帮助开发者构建高质量的服务端应用。