Node.js
一、Node.js 基础与模块系统
1. Node.js 是什么?
定义 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境(Runtime),它使得 JavaScript 可以在服务端运行,而不再局限于浏览器环境。
原理 Node.js 由 C++ 编写的底层核心和 JavaScript 编写的高层 API 组成。其架构分为四层:
┌─────────────────────────────────────────────────┐
│ Node.js API (JavaScript) │ ← 用户层
├─────────────────────────────────────────────────┤
│ Node.js Bindings (C++) │ ← 绑定层
├─────────────────────────────────────────────────┤
│ V8 引擎 + Libuv + ... │ ← 核心层
├─────────────────────────────────────────────────┤
│ 操作系统 (Linux/Windows/Mac) │ ← 系统层
└─────────────────────────────────────────────────┘
核心组成部分
- V8 引擎: Google 开发的 JavaScript 引擎,负责 JS 代码的编译和执行
- Libuv: 跨平台的异步 I/O 库,提供事件循环和线程池
- C-ares: 处理 DNS 查询
- OpenSSL: 提供加密和 TLS 支持
- HTTP Parser: 解析 HTTP 协议
- Zlib: 提供压缩功能
示例:创建简单的 HTTP 服务器
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, Node.js!\n');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
常见误区
- ❌ Node.js 是一个框架 → ✅ Node.js 是运行时环境,不是框架
- ❌ Node.js 是多线程的 → ✅ Node.js 主线程是单线程的,但底层 Libuv 使用线程池处理部分 I/O
- ❌ Node.js 只适合 I/O 密集型 → ✅ 虽然擅长 I/O 密集型,但通过 Worker Threads 也能处理 CPU 密集型任务
2. Node.js 特点
核心特点
| 特点 | 说明 | 原理 |
|---|---|---|
| 单线程 | 主线程只有一个,避免多线程上下文切换开销 | V8 引擎单线程执行 JS 代码 |
| 非阻塞 I/O | I/O 操作不会阻塞主线程 | Libuv 事件循环 + 线程池 |
| 事件驱动 | 通过事件循环处理异步操作 | 事件驱动架构 |
| 异步编程 | 大量使用回调、Promise、async/await | 避免阻塞,提高并发能力 |
| 跨平台 | 支持 Windows、macOS、Linux | Libuv 跨平台抽象层 |
| 丰富的生态系统 | NPM 拥有全球最大的包仓库 | package.json + registry |
优势场景
- I/O 密集型应用:Web 服务器、API 网关、代理服务器
- 实时应用:聊天室、在线游戏、实时数据分析
- 工具链:构建工具(Webpack、Vite)、脚手架(CLI)
- 微服务:轻量级、快速启动、适合容器化部署
劣势场景
- CPU 密集型任务:图像处理、视频编码、机器学习(可通过 Worker Threads 缓解)
3. Node.js 应用场景
主要应用场景
| 场景 | 说明 | 技术栈示例 |
|---|---|---|
| Web 服务器 | 构建 RESTful API、GraphQL 服务 | Express、Koa、Fastify |
| SSR(服务端渲染) | 提升首屏加载速度和 SEO | Next.js、Nuxt.js |
| 实时应用 | WebSocket 实时通信 | Socket.io、ws |
| 构建工具 | 前端工程化 | Webpack、Vite、Rollup |
| CLI 工具 | 命令行工具开发 | Commander、Inquirer |
| 微服务 | 轻量级服务拆分 | NestJS、Fastify |
| 自动化脚本 | 文件处理、部署脚本 | fs、child_process |
| 中间层(BFF) | 聚合多个后端 API | GraphQL、REST 代理 |
| 流式处理 | 大文件处理、数据转换 | Stream API |
SSR 示例(Nuxt.js)
// nuxt.config.js
export default {
target: 'server',
ssr: true,
// 服务端渲染,提升首屏加载速度和 SEO
}
BFF 示例
const express = require('express');
const axios = require('axios');
const app = express();
app.get('/api/user-data', async (req, res) => {
// 聚合多个后端服务
const [user, orders, notifications] = await Promise.all([
axios.get('http://user-service/api/user'),
axios.get('http://order-service/api/orders'),
axios.get('http://notification-service/api/notifications')
]);
res.json({ user: user.data, orders: orders.data, notifications: notifications.data });
});
4. Node.js 模块系统
定义
Node.js 模块系统遵循 CommonJS 规范,将代码组织为独立的模块,每个模块有自己的作用域,通过 require 加载,通过 module.exports 导出。
模块类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 核心模块 | Node.js 内置模块,无需安装 | fs, http, path, events |
| 文件模块 | 用户创建的本地模块 | ./utils, ../config/db |
| 第三方模块 | NPM 安装的模块 | express, lodash, axios |
模块加载优先级
- 核心模块(优先级最高)
- NPM 安装的模块(
node_modules) - 本地文件模块(需要路径标识)
模块包装器 Node.js 在执行模块前会将其包装在一个函数中:
(function(exports, require, module, __filename, __dirname) {
// 模块的实际代码
const fs = require('fs');
module.exports = { readFile: fs.readFile };
});
这解释了为什么每个模块中的 var、let、const 声明都是局部变量,不会污染全局作用域。
示例
// math.js - 模块定义
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
module.exports = { add, subtract };
// app.js - 模块使用
const math = require('./math');
console.log(math.add(1, 2)); // 3
5. CommonJS 规范
定义 CommonJS 是 JavaScript 的模块规范之一,最初为服务端 JavaScript 设计。Node.js 采用了 CommonJS 规范的大部分特性。
核心 API
require(id): 加载模块module.exports: 导出模块内容exports:module.exports的引用
特性
- 同步加载:
require是同步的,会阻塞后续代码执行 - 运行时加载: 在代码运行时确定依赖关系
- 值拷贝: 导出的是值的拷贝(对象是引用拷贝)
- 缓存机制: 模块加载后会被缓存,再次加载直接返回缓存
// a.js
let count = 0;
function increment() { count++; return count; }
module.exports = { increment, getCount: () => count };
// b.js
const { increment, getCount } = require('./a');
console.log(increment()); // 1
console.log(getCount()); // 1
// 导出的是值的拷贝,但对象/函数是引用
与 ES Modules 对比
| 特性 | CommonJS | ES Modules |
|---|---|---|
| 语法 | require / module.exports | import / export |
| 加载方式 | 同步加载 | 异步加载(静态分析) |
| 加载时机 | 运行时 | 编译时(静态解析) |
| 输出值 | 值的拷贝 | 值的引用(动态绑定) |
| this 指向 | 指向当前模块 | undefined |
| Tree Shaking | 不支持 | 支持 |
| 顶层 await | 不支持 | 支持 |
6. require 原理
加载流程
require 的完整加载过程分为五个阶段:
require(id)
↓
1. 路径解析 (Module._resolveFilename)
↓
2. 缓存检查 (Module._cache)
↓
3. 创建模块实例 (new Module)
↓
4. 加载模块 (module.load)
↓
5. 编译执行 (module._compile)
↓
返回 module.exports
源码简化分析
// Node.js 内部简化实现
Module.prototype.require = function(id) {
// 1. 解析路径
const filename = Module._resolveFilename(id, this);
// 2. 检查缓存
if (Module._cache[filename]) {
return Module._cache[filename].exports;
}
// 3. 创建新模块
const module = new Module(filename);
Module._cache[filename] = module;
// 4. 加载模块
module.load(filename);
// 5. 返回导出
return module.exports;
};
路径解析规则
- 核心模块:直接返回模块名(如
fs) - 相对路径:拼接当前文件所在目录
- 绝对路径:直接使用
- 无路径标识:在
node_modules中查找
7. module.exports 与 exports
关系说明
exports是module.exports的引用- 最终导出的是
module.exports - 对
exports的属性赋值等价于对module.exports赋值 - 直接给
exports重新赋值会断开引用关系
// 正确用法 1:给 exports 添加属性
exports.name = 'Node.js';
exports.version = '18';
// 等价于 module.exports.name = 'Node.js';
// 正确用法 2:直接赋值 module.exports
module.exports = {
name: 'Node.js',
version: '18'
};
// ❌ 错误用法:给 exports 重新赋值
exports = { name: 'Node.js' }; // 无效!断开了与 module.exports 的引用
引用关系图解
初始状态:
exports ──→ {} ←── module.exports
给 exports 添加属性:
exports.name = 'xxx'
exports ──→ { name: 'xxx' } ←── module.exports
给 exports 重新赋值(断开引用):
exports = {}
exports ──→ {} (新的空对象)
{ name: 'xxx' } ←── module.exports (最终导出的)
8. 模块缓存
原理
Node.js 会缓存已加载的模块,再次 require 时直接返回缓存,不会重新执行模块代码。缓存存储在 require.cache 中。
// a.js
console.log('a.js 被加载');
module.exports = { count: 0 };
// b.js
const a1 = require('./a'); // 输出: a.js 被加载
const a2 = require('./a'); // 无输出,使用缓存
console.log(a1 === a2); // true,同一个对象
清除缓存
// 清除指定模块缓存
delete require.cache[require.resolve('./a')];
// 清除所有缓存
Object.keys(require.cache).forEach(key => {
delete require.cache[key];
});
使用场景
- 配置文件热更新
- 测试用例隔离
- 插件系统动态加载
9. 模块加载机制
文件查找规则
当 require('./utils') 时,Node.js 按以下顺序查找:
./utils(精确匹配文件)./utils.js./utils.json./utils.node(C++ 扩展)./utils/index.js(作为目录,查找 index 文件)
目录模块查找
如果 utils 是目录:
- 查找
./utils/package.json中的main字段 - 查找
./utils/index.js - 查找
./utils/index.json - 查找
./utils/index.node
node_modules 查找
当 require('express') 时,从当前目录开始向上逐层查找:
./node_modules/express
../node_modules/express
../../node_modules/express
../../../node_modules/express
...
10. 循环依赖
问题描述 当模块 A 引用模块 B,模块 B 又引用模块 A 时,形成循环依赖。
// a.js
const b = require('./b');
console.log('在 a.js 中,b 的值为:', b);
module.exports = { name: 'a' };
// b.js
const a = require('./a');
console.log('在 b.js 中,a 的值为:', a);
module.exports = { name: 'b' };
// main.js
require('./a');
// 输出:
// 在 b.js 中,a 的值为: {} (空对象,a.js 尚未执行完)
// 在 a.js 中,b 的值为: { name: 'b' }
解决策略
- 推迟引用: 在函数内部 require
// a.js
module.exports = {
getName() {
const b = require('./b'); // 延迟加载
return b.name;
}
};
- 抽取公共模块: 将共同依赖抽到独立模块
- 重新设计架构: 消除循环依赖,改为单向依赖
二、Buffer 与 Stream
11. Buffer
定义 Buffer 是 Node.js 用于处理二进制数据的类数组对象。在 JavaScript 原生不支持二进制数据时,Buffer 填补了这一空白。
特点
- 大小固定,创建后不能调整
- 内存分配在 V8 堆外(由 C++ 层管理)
- 基于 TypedArray(Uint8Array)
创建方式
// 从字符串创建
const buf1 = Buffer.from('Hello', 'utf-8');
// 从数组创建
const buf2 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
// 分配指定大小(已废弃旧 API,不推荐使用 allocUnsafe)
const buf3 = Buffer.alloc(10); // 安全初始化(清零)
const buf4 = Buffer.allocUnsafe(10); // 快速但不安全(可能包含旧数据)
// 拼接 Buffer
const combined = Buffer.concat([buf1, buf2]);
常见操作
const buf = Buffer.from('Node.js');
// 转字符串
console.log(buf.toString('utf-8')); // 'Node.js'
console.log(buf.toString('hex')); // '4e6f64652e6a73'
console.log(buf.toString('base64')); // 'Tm9kZS5qcw=='
// 读取/写入
buf.writeUInt8(65, 0); // 在位置 0 写入 65 ('A')
console.log(buf.readUInt8(0)); // 65
// 比较
Buffer.compare(buf1, buf2); // 返回 -1, 0, 1
buf.equals(buf2); // 返回 boolean
应用场景
- 文件读写
- 网络数据传输
- 加密/解密
- 图片/视频处理
12. Stream
定义 Stream(流)是 Node.js 中处理流式数据的抽象接口,用于连续读取或写入大量数据,而不需要将全部数据加载到内存中。
流的类型
| 类型 | 说明 | 示例 |
|---|---|---|
| Readable | 可读流,数据源 | fs.createReadStream |
| Writable | 可写流,数据目标 | fs.createWriteStream |
| Duplex | 双工流,既可读又可写 | net.Socket |
| Transform | 转换流,读取时可修改数据 | zlib.createGzip |
可读流示例
const fs = require('fs');
// 方式 1:事件监听
const readStream = fs.createReadStream('./large-file.txt', {
highWaterMark: 64 * 1024, // 64KB 缓冲区
encoding: 'utf-8'
});
readStream.on('data', chunk => {
console.log(`接收到 ${chunk.length} 字节`);
});
readStream.on('end', () => {
console.log('读取完成');
});
// 方式 2:异步迭代
(async () => {
for await (const chunk of readStream) {
console.log(chunk);
}
})();
可写流示例
const writeStream = fs.createWriteStream('./output.txt');
writeStream.write('Hello ');
writeStream.write('World');
writeStream.end(); // 结束写入
writeStream.on('finish', () => {
console.log('写入完成');
});
管道(Pipe)
const fs = require('fs');
const zlib = require('zlib');
// 文件压缩
fs.createReadStream('./input.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('./input.txt.gz'));
// pipe 自动处理背压(backpressure)
背压(Backpressure)处理 当写入速度跟不上读取速度时,会产生背压:
const fs = require('fs');
const readStream = fs.createReadStream('./large.txt');
const writeStream = fs.createWriteStream('./output.txt');
// 手动处理背压
readStream.on('data', chunk => {
const shouldContinue = writeStream.write(chunk);
if (!shouldContinue) {
readStream.pause(); // 暂停读取
console.log('背压:暂停读取');
}
});
writeStream.on('drain', () => {
readStream.resume(); // 缓冲区清空,恢复读取
console.log('背压解除:恢复读取');
});
三、异步 I/O 与事件循环
13. 异步 I/O
定义 异步 I/O 是指在执行 I/O 操作(文件读写、网络请求、数据库查询)时,不会阻塞主线程,而是将操作交给底层线程池或系统 API,完成后通过回调通知主线程。
同步 vs 异步
const fs = require('fs');
// 同步(阻塞)
const data = fs.readFileSync('./file.txt', 'utf-8');
console.log(data); // 等待文件读取完成
// 异步(非阻塞)
fs.readFile('./file.txt', 'utf-8', (err, data) => {
console.log(data); // 读取完成后执行
});
console.log('继续执行其他代码'); // 不会等待
底层实现
- 文件 I/O: 使用 Libuv 线程池(默认 4 个线程)
- 网络 I/O: 使用操作系统 epoll/kqueue/IOCP(非阻塞系统调用)
- DNS: 使用 C-ares 或线程池
14. Node.js 事件循环
定义 事件循环(Event Loop)是 Node.js 实现非阻塞 I/O 的核心机制。它是一个无限循环,不断检查是否有待处理的任务,并按阶段执行。
事件循环流程图
┌───────────────────────────┐
┌─>│ timers │ ← setTimeout, setInterval
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ ← 上一轮未执行的 I/O 回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ ← 内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ ← I/O 回调、fs.readFile 等
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ ← setImmediate
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──│ close callbacks │ ← close 事件回调
└───────────────────────────┘
事件循环核心要点
- 事件循环是单线程的
- 每个阶段都有一个 FIFO 回调队列
- 只有队列为空或达到系统限制时,才会进入下一阶段
process.nextTick和Promise.then在每个阶段结束后立即执行
15. 事件循环各阶段详解
timers 阶段
- 时机: 事件循环的第一阶段
- 执行:
setTimeout和setInterval的回调 - 注意: 延迟时间是最小等待时间,实际执行时间可能更长(受系统负载影响)
setTimeout(() => {
console.log('setTimeout 回调');
}, 100);
// 至少等待 100ms 后在 timers 阶段执行
pending callbacks 阶段
- 执行: 上一轮循环中未执行的 I/O 回调
- 场景: 网络错误回调、TCP 连接异常等
idle, prepare 阶段
- 用途: Node.js 内部使用
- 开发者一般不直接涉及
poll 阶段
- 最重要的阶段
- 功能:
- 执行 I/O 回调(
fs.readFile、http.request等) - 检查 timers 队列,如果到期则回到 timers 阶段
- 执行 I/O 回调(
- 行为:
- 如果 poll 队列不为空:同步执行所有回调
- 如果 poll 队列为空:
- 如果有
setImmediate: 立即进入 check 阶段 - 如果没有
setImmediate: 等待新的 I/O 回调
- 如果有
fs.readFile('./file.txt', 'utf-8', (err, data) => {
// 在 poll 阶段执行
console.log(data);
});
check 阶段
- 执行:
setImmediate的回调 - 特点: 在 poll 阶段结束后立即执行
setImmediate(() => {
console.log('setImmediate 回调');
});
close callbacks 阶段
- 执行: 关闭事件的回调(如
socket.on('close'))
const server = net.createServer();
server.on('close', () => {
// 在 close callbacks 阶段执行
console.log('服务器已关闭');
});
16. setImmediate
定义
setImmediate 将回调添加到事件循环的 check 阶段,在当前轮次的 I/O 操作完成后执行。
console.log('start');
setImmediate(() => {
console.log('setImmediate');
});
console.log('end');
// 输出: start → end → setImmediate
与 setTimeout(fn, 0) 的区别
setImmediate: 在 poll 阶段后执行(确定性更高)setTimeout(fn, 0): 在 timers 阶段执行(至少等待 1ms)
17. process.nextTick
定义
process.nextTick 将回调添加到当前阶段的末尾,在下一个阶段开始前执行。它不属于事件循环的任何阶段,优先级最高。
console.log('start');
process.nextTick(() => {
console.log('nextTick');
});
console.log('end');
// 输出: start → end → nextTick
递归调用可能导致阻塞
// ❌ 危险:阻塞事件循环
function block() {
process.nextTick(block);
}
block(); // 永远无法进入下一阶段
18. setImmediate vs setTimeout
| 对比项 | setTimeout(fn, 0) | setImmediate(fn) |
|---|---|---|
| 执行阶段 | timers 阶段 | check 阶段 |
| 最小延迟 | 约 1ms | 当前轮次 I/O 后 |
| 确定性 | 较低(受系统影响) | 较高 |
| 适用场景 | 需要延迟执行 | I/O 后立即执行 |
执行顺序测试
// 在主模块中执行(结果不确定)
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 可能: timeout → immediate
// 也可能: immediate → timeout
// 在 I/O 回调中执行(结果确定)
const fs = require('fs');
fs.readFile('./file.txt', () => {
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
});
// 永远是: immediate → timeout
19. process.nextTick vs setImmediate
| 对比项 | process.nextTick | setImmediate |
|---|---|---|
| 执行时机 | 当前阶段结束、下一阶段开始前 | check 阶段 |
| 优先级 | 最高 | 较低 |
| 递归风险 | 容易阻塞事件循环 | 不会阻塞 |
| 适用场景 | 需要立即执行,但非同步 | I/O 回调后执行 |
执行顺序
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('Promise'));
// 输出: nextTick → Promise → setTimeout/setImmediate
20. Node.js 与浏览器事件循环的区别
| 对比项 | Node.js | 浏览器 |
|---|---|---|
| 事件循环模型 | 6 个阶段(Libuv) | 宏任务 + 微任务 |
| 微任务处理 | 每个阶段结束后 | 当前宏任务结束后 |
| task 概念 | 无明确 task 概念 | 宏任务(task) |
| 特有 API | setImmediate、process.nextTick | MessageChannel、requestAnimationFrame |
| I/O 模型 | Libuv 线程池 + epoll | 浏览器事件队列 |
浏览器事件循环
宏任务队列(MacroTask)
├─ setTimeout
├─ setInterval
├─ setImmediate(仅 IE)
├─ I/O
└─ UI 渲染
微任务队列(MicroTask)
├─ Promise.then/catch/finally
├─ MutationObserver
├─ queueMicrotask
└─ process.nextTick(Node.js 独有)
四、文件系统操作
21. fs 模块
定义
fs(File System)模块是 Node.js 提供的文件系统操作核心模块,支持同步和异步两种方式。
导入方式
const fs = require('fs');
const fsPromises = require('fs/promises');
22. fs.readFile
功能: 异步读取文件内容
// 回调风格
fs.readFile('./file.txt', 'utf-8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log(data);
});
// Promise 风格
const fsPromises = require('fs/promises');
async function read() {
try {
const data = await fsPromises.readFile('./file.txt', 'utf-8');
console.log(data);
} catch (err) {
console.error(err);
}
}
23. fs.writeFile
功能: 异步写入文件(覆盖写入)
fs.writeFile('./output.txt', 'Hello World', 'utf-8', (err) => {
if (err) throw err;
console.log('写入成功');
});
// 追加模式
fs.writeFile('./output.txt', 'New Content', { flag: 'a' }, (err) => {
// ...
});
24. fs.appendFile
功能: 追加内容到文件末尾
fs.appendFile('./log.txt', 'New log entry\n', (err) => {
if (err) throw err;
console.log('追加成功');
});
25. fs.unlink
功能: 删除文件
fs.unlink('./temp.txt', (err) => {
if (err) throw err;
console.log('文件已删除');
});
26. fs.mkdir / fs.rmdir
// 创建目录
fs.mkdir('./new-dir', { recursive: true }, (err) => {
// recursive: true 时,父目录不存在会自动创建
});
// 删除目录(Node.js 14.14+ 推荐使用 fs.rm)
fs.rm('./new-dir', { recursive: true }, (err) => {
// 删除目录及其内容
});
27. fs.readdir
功能: 读取目录内容
fs.readdir('./my-dir', (err, files) => {
if (err) throw err;
console.log(files); // ['file1.txt', 'file2.js', ...]
});
// 递归读取子目录
fs.readdir('./my-dir', { recursive: true }, (err, files) => {
console.log(files);
});
28. fs.stat
功能: 获取文件/目录信息
fs.stat('./file.txt', (err, stats) => {
if (err) throw err;
console.log('文件大小:', stats.size);
console.log('是否是文件:', stats.isFile());
console.log('是否是目录:', stats.isDirectory());
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
});
29. fs.watch
功能: 监听文件/目录变化
const watcher = fs.watch('./my-dir', (eventType, filename) => {
console.log(`文件 ${filename} 发生了 ${eventType} 事件`);
});
watcher.on('error', (err) => {
console.error('监听错误:', err);
});
// 停止监听
// watcher.close();
注意: fs.watch 在不同操作系统上行为可能不一致,生产环境推荐使用 chokidar 库。
30. 同步与异步文件操作
| 特性 | 同步操作 | 异步操作 |
|---|---|---|
| 方法名 | readFileSync, writeFileSync | readFile, writeFile |
| 阻塞 | 阻塞主线程 | 不阻塞 |
| 性能 | 差 | 好 |
| 适用场景 | 启动时读取配置、脚本 | 服务器运行时 |
// ❌ 不推荐:阻塞主线程
const data = fs.readFileSync('./config.json', 'utf-8');
// ✅ 推荐:异步操作
fs.readFile('./config.json', 'utf-8', (err, data) => {
// 处理数据
});
31. 文件描述符
定义 文件描述符(File Descriptor)是操作系统为打开的文件分配的非负整数标识符。
// 打开文件获取描述符
fs.open('./file.txt', 'r', (err, fd) => {
if (err) throw err;
console.log('文件描述符:', fd); // 例如: 3
// 使用描述符读取
const buffer = Buffer.alloc(1024);
fs.read(fd, buffer, 0, buffer.length, 0, (err, bytesRead) => {
console.log('读取字节数:', bytesRead);
fs.close(fd, () => {}); // 关闭文件
});
});
注意事项
- 必须在使用后调用
fs.close()关闭,否则会导致文件描述符泄漏 - 操作系统对打开的文件描述符数量有限制
五、HTTP 服务器
32. http 模块
定义
http 模块是 Node.js 内置的 HTTP 服务器和客户端模块,用于创建 Web 服务器和发起 HTTP 请求。
33. 创建 HTTP 服务器
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Hello World</h1>');
});
server.listen(3000, 'localhost', () => {
console.log('Server: http://localhost:3000');
});
34. request 对象
request 对象(IncomingMessage)包含客户端请求信息:
const http = require('http');
http.createServer((req, res) => {
console.log('请求方法:', req.method); // GET, POST, ...
console.log('请求 URL:', req.url); // /api/users
console.log('请求头:', req.headers); // { host: 'localhost:3000', ... }
console.log('HTTP 版本:', req.httpVersion); // 1.1
// 解析 URL 参数
const url = require('url');
const parsed = url.parse(req.url, true);
console.log('查询参数:', parsed.query);
// 接收 POST 数据
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
console.log('请求体:', body);
});
}).listen(3000);
35. response 对象
response 对象(ServerResponse)用于向客户端发送响应:
http.createServer((req, res) => {
// 设置状态码和响应头
res.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
});
// 发送响应
res.write('Partial content ');
res.end(JSON.stringify({ status: 'ok' }));
// 设置状态码(单独)
res.statusCode = 404;
res.statusMessage = 'Not Found';
}).listen(3000);
36. 路由实现
简单路由
const http = require('http');
const url = require('url');
const routes = {
'GET /': (req, res) => res.end('Home'),
'GET /users': (req, res) => res.end('Users List'),
'POST /users': (req, res) => res.end('Create User'),
};
http.createServer((req, res) => {
const pathname = url.parse(req.url).pathname;
const key = `${req.method} ${pathname}`;
if (routes[key]) {
routes[key](req, res);
} else {
res.writeHead(404);
res.end('Not Found');
}
}).listen(3000);
37. 静态文件服务
const http = require('http');
const fs = require('fs');
const path = require('path');
const mimeTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.png': 'image/png',
'.json': 'application/json',
};
http.createServer((req, res) => {
let filePath = '.' + req.url;
if (filePath === './') filePath = './index.html';
const extname = path.extname(filePath);
const contentType = mimeTypes[extname] || 'application/octet-stream';
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end('File not found');
} else {
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
}
});
}).listen(3000);
六、Express/Koa 框架
38. Express
定义 Express 是一个轻量级的 Node.js Web 框架,提供了路由、中间件、模板引擎等功能。
基本使用
const express = require('express');
const app = express();
// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 路由
app.get('/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
});
app.post('/users', (req, res) => {
console.log(req.body);
res.status(201).json({ message: 'User created' });
});
// 启动
app.listen(3000, () => {
console.log('Express server started');
});
39. Express 路由
路由定义
const express = require('express');
const router = express.Router();
// 基本路由
router.get('/users', (req, res) => { /* ... */ });
router.post('/users', (req, res) => { /* ... */ });
router.put('/users/:id', (req, res) => { /* ... */ });
router.delete('/users/:id', (req, res) => { /* ... */ });
// 路由参数
router.get('/users/:id', (req, res) => {
console.log(req.params.id); // 路径参数
console.log(req.query.page); // 查询参数
res.json({ id: req.params.id });
});
// 挂载路由
app.use('/api', router);
40. Express 中间件
中间件类型
// 应用级中间件
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
// 路由级中间件
router.use((req, res, next) => {
// 特定路由前的中间件
next();
});
// 错误处理中间件(必须有 4 个参数)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
});
// 内置中间件
app.use(express.static('public'));
app.use(express.json());
// 第三方中间件
app.use(require('cors')());
app.use(require('morgan')('dev'));
中间件执行顺序
app.use((req, res, next) => {
console.log('1. 日志中间件');
next();
});
app.get('/users', (req, res, next) => {
console.log('2. 路由中间件');
next();
}, (req, res) => {
console.log('3. 响应中间件');
res.json({ users: [] });
});
// 输出: 1. 日志中间件 → 2. 路由中间件 → 3. 响应中间件
41. Express 错误处理
// 方式 1:错误处理中间件
app.use((err, req, res, next) => {
if (err.type === 'entity.parse.failed') {
return res.status(400).json({ error: 'Invalid JSON' });
}
res.status(500).json({ error: 'Server Error' });
});
// 方式 2:异步路由错误处理
app.get('/users', async (req, res, next) => {
try {
const users = await db.find();
res.json(users);
} catch (err) {
next(err); // 交给错误处理中间件
}
});
// 方式 3:express-async-errors 包
require('express-async-errors');
app.get('/users', async (req, res) => {
const users = await db.find(); // 自动捕获异常
res.json(users);
});
42. Koa
定义 Koa 是 Express 原班人马打造的新一代 Node.js Web 框架,基于 ES6 Generator/async-await,提供更优雅的异步处理。
基本使用
const Koa = require('koa');
const app = new Koa();
// 中间件
app.use(async (ctx, next) => {
console.log('请求开始');
await next();
console.log('请求结束');
});
app.use(async (ctx) => {
ctx.body = { message: 'Hello Koa' };
});
app.listen(3000);
43. Koa 中间件
洋葱模型 Koa 的中间件采用"洋葱模型",请求从外向内穿过中间件,响应从内向外返回。
app.use(async (ctx, next) => {
console.log('1. 开始');
await next();
console.log('1. 结束');
});
app.use(async (ctx, next) => {
console.log('2. 开始');
await next();
console.log('2. 结束');
});
app.use(async (ctx) => {
console.log('3. 响应');
ctx.body = 'Hello';
});
// 输出:
// 1. 开始
// 2. 开始
// 3. 响应
// 2. 结束
// 1. 结束
44. Koa 与 Express 的区别
| 对比项 | Express | Koa |
|---|---|---|
| 异步处理 | 回调函数 | async/await |
| 中间件模型 | 线性模型 | 洋葱模型 |
| 内置功能 | 路由、模板、静态文件 | 仅核心功能 |
| 错误处理 | try-catch 或回调 | try-catch |
| context 对象 | req, res 分离 | ctx 统一封装 |
| 体积 | 较大 | 更小 |
| 生态 | 更成熟 | 逐渐完善 |
| 响应设置 | res.send(), res.json() | ctx.body = ... |
选择策略
- 选 Express:项目需要快速开发、需要丰富的中间件生态、团队熟悉 Express
- 选 Koa:需要更优雅的异步处理、喜欢轻量级框架、需要洋葱模型能力
45. koa-router
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
router.get('/users', async (ctx) => {
ctx.body = [{ id: 1, name: 'Alice' }];
});
router.post('/users', async (ctx) => {
ctx.body = { message: 'Created', user: ctx.request.body };
ctx.status = 201;
});
router.get('/users/:id', async (ctx) => {
ctx.body = { id: ctx.params.id };
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);
46. koa-bodyparser
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyParser());
app.use(async (ctx) => {
console.log(ctx.request.body); // 解析后的请求体
ctx.body = { received: ctx.request.body };
});
47. koa-static
const Koa = require('koa');
const serve = require('koa-static');
const app = new Koa();
// 静态文件服务
app.use(serve('./public'));
app.listen(3000);
// http://localhost:3000/style.css → ./public/style.css
七、数据库操作
48. MySQL 连接
const mysql = require('mysql2/promise');
async function connect() {
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydb'
});
return connection;
}
// 使用连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydb',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
49. MySQL CRUD
// 查询
const [rows] = await connection.query('SELECT * FROM users WHERE age > ?', [18]);
// 插入
const [result] = await connection.query(
'INSERT INTO users (name, email) VALUES (?, ?)',
['Alice', 'alice@example.com']
);
console.log(result.insertId);
// 更新
await connection.query(
'UPDATE users SET name = ? WHERE id = ?',
['Bob', 1]
);
// 删除
await connection.query('DELETE FROM users WHERE id = ?', [1]);
50. MySQL 事务
async function transfer(fromId, toId, amount) {
const connection = await mysql.createConnection({ /* ... */ });
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();
console.log('转账成功');
} catch (err) {
await connection.rollback();
console.error('转账失败:', err);
} finally {
connection.end();
}
}
51. MongoDB 连接与 CRUD
const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');
async function run() {
await client.connect();
const db = client.db('mydb');
const collection = db.collection('users');
// 创建
await collection.insertOne({ name: 'Alice', age: 25 });
// 查询
const user = await collection.findOne({ name: 'Alice' });
const users = await collection.find({ age: { $gt: 18 } }).toArray();
// 更新
await collection.updateOne(
{ name: 'Alice' },
{ $set: { age: 26 } }
);
// 删除
await collection.deleteOne({ name: 'Alice' });
}
52. Mongoose
const mongoose = require('mongoose');
// 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 }
});
// 虚拟属性
userSchema.virtual('birthYear').get(function() {
return new Date().getFullYear() - this.age;
});
// Model
const User = mongoose.model('User', userSchema);
// CRUD
async function run() {
await mongoose.connect('mongodb://localhost:27017/mydb');
const user = await User.create({ name: 'Alice', email: 'a@b.com', age: 25 });
const found = await User.findOne({ name: 'Alice' });
await User.updateOne({ _id: user._id }, { age: 26 });
await User.deleteOne({ _id: user._id });
}
53. Redis
连接与基本操作
const redis = require('redis');
const client = redis.createClient({ url: 'redis://localhost:6379' });
async function run() {
await client.connect();
// String
await client.set('name', 'Alice');
const name = await client.get('name');
// Hash
await client.hSet('user:1', { name: 'Alice', age: 25 });
const user = await client.hGetAll('user:1');
// List
await client.lPush('todos', 'task1', 'task2');
const todos = await client.lRange('todos', 0, -1);
// Set
await client.sAdd('tags', 'nodejs', 'javascript');
const tags = await client.sMembers('tags');
// 设置过期时间
await client.setEx('temp', 60, 'value'); // 60秒后过期
}
Redis 缓存策略
// Cache-Aside 模式
async function getUser(id) {
// 1. 先查缓存
const cached = await client.get(`user:${id}`);
if (cached) {
return JSON.parse(cached);
}
// 2. 查数据库
const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
// 3. 写入缓存
await client.setEx(`user:${id}`, 3600, JSON.stringify(user));
return user;
}
Redis 数据类型
| 类型 | 说明 | 常用命令 |
|---|---|---|
| String | 字符串 | SET, GET, INCR |
| Hash | 哈希表 | HSET, HGET, HGETALL |
| List | 链表 | LPUSH, RPUSH, LPOP |
| Set | 无序集合 | SADD, SMEMBERS, SISMEMBER |
| ZSet | 有序集合 | ZADD, ZRANGE, ZSCORE |
八、NPM 包管理
54. NPM
定义 NPM(Node Package Manager)是 Node.js 的包管理工具,用于安装、发布、管理 JavaScript 包。
55. package.json
{
"name": "my-app",
"version": "1.0.0",
"description": "My Node.js App",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest",
"build": "webpack --mode production"
},
"dependencies": {
"express": "^4.18.0",
"mongoose": "^7.0.0"
},
"devDependencies": {
"jest": "^29.0.0",
"nodemon": "^3.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
版本前缀说明
| 前缀 | 含义 | 示例 |
|---|---|---|
^ | 允许小版本升级 | ^4.18.0 → 4.19.0 ✅ |
~ | 允许补丁版本升级 | ~4.18.0 → 4.18.1 ✅ |
* | 任意版本 | * → 最新版 |
| 无前缀 | 锁定版本 | 4.18.0 → 仅 4.18.0 |
56. npm install
# 安装所有依赖
npm install
# 安装指定包
npm install express
# 安装为开发依赖
npm install -D jest
# 全局安装
npm install -g nodemon
# 精确版本安装
npm install express@4.17.1
安装机制
- 依赖解析: 读取 package.json,解析依赖树
- 注册表查询: 从 NPM registry 下载包
- 缓存: 下载的包缓存到
~/.npm/_cacache - 扁平化安装: npm 5+ 尽量将依赖安装到根
node_modules - 生成 lock 文件: 锁定依赖版本
57. npm scripts
{
"scripts": {
"start": "node index.js",
"dev": "NODE_ENV=development nodemon index.js",
"build": "tsc",
"test": "jest --coverage",
"lint": "eslint .",
"prestart": "npm run build",
"posttest": "npm run lint"
}
}
执行
npm run dev
npm run test
pre/post 钩子
npm run start
# 自动执行: prestart → start → poststart
58. npm vs yarn vs pnpm
| 对比项 | npm | yarn | pnpm |
|---|---|---|---|
| 速度 | 较慢 | 快 | 最快 |
| 磁盘空间 | 每个项目独立安装 | 全局缓存 | 内容寻址存储 |
| 依赖管理 | 扁平化(幻影依赖) | 扁平化(strict) | 严格隔离 |
| Lock 文件 | package-lock.json | yarn.lock | pnpm-lock.yaml |
| 离线安装 | 部分支持 | 支持 | 支持 |
| 工作区 | 支持 | 支持 | 支持 |
59. 发布包
# 1. 登录
npm login
# 2. 版本号更新
npm version patch # 1.0.0 → 1.0.1
npm version minor # 1.0.0 → 1.1.0
npm version major # 1.0.0 → 2.0.0
# 3. 发布
npm publish
# 4. 撤销发布(72小时内)
npm unpublish my-package@1.0.0
60. npx
# 执行本地安装的 CLI 工具
npx jest
# 临时执行(不安装)
npx create-react-app my-app
# 指定版本
npx typescript@5.0.0 tsc --version
九、中间件机制
61. 中间件
定义 中间件(Middleware)是位于请求和响应之间的处理函数,可以访问请求对象、响应对象和 next 函数。
// 基本中间件结构
function middleware(req, res, next) {
// 请求处理
console.log(`${req.method} ${req.url}`);
// 传递到下一个中间件
next();
// 响应后处理(可选)
}
62. 中间件原理
Express 中间件实现原理
// 简化版 Express 中间件机制
class Application {
constructor() {
this.middlewares = [];
}
use(fn) {
this.middlewares.push(fn);
}
handle(req, res) {
let index = 0;
const next = () => {
if (index >= this.middlewares.length) return;
const middleware = this.middlewares[index++];
middleware(req, res, next);
};
next();
}
}
63. 中间件执行顺序
中间件按注册顺序依次执行,先注册的先执行。
// 顺序:A → B → C → B(返回) → A(返回)
app.use((req, res, next) => {
console.log('A 前置');
next();
console.log('A 后置');
});
app.use((req, res, next) => {
console.log('B 前置');
next();
console.log('B 后置');
});
app.use((req, res) => {
console.log('C 处理');
res.send('done');
});
64. 中间件类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 应用级 | 全局生效 | app.use() |
| 路由级 | 特定路由生效 | router.use() |
| 错误处理 | 4 个参数 | (err, req, res, next) => {} |
| 内置 | Express/Koa 内置 | express.json() |
| 第三方 | NPM 安装 | cors(), morgan() |
十、进程与线程
65. 进程 vs 线程
| 对比项 | 进程 | 线程 |
|---|---|---|
| 定义 | 程序的一次执行实例 | 进程内的执行单元 |
| 资源 | 独立内存空间 | 共享进程内存 |
| 开销 | 大(创建/切换) | 小 |
| 通信 | IPC(进程间通信) | 共享内存 |
| 隔离性 | 高 | 低 |
| Node.js | child_process, cluster | worker_threads |
66. child_process
spawn - 启动子进程
const { spawn } = require('child_process');
const child = spawn('node', ['worker.js'], {
stdio: ['pipe', 'pipe', 'pipe']
});
child.stdout.on('data', (data) => {
console.log(`子进程输出: ${data}`);
});
child.on('close', (code) => {
console.log(`子进程退出码: ${code}`);
});
exec - 执行命令
const { exec } = require('child_process');
exec('ls -la', (error, stdout, stderr) => {
if (error) {
console.error(`执行错误: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
});
execFile - 执行可执行文件
const { execFile } = require('child_process');
execFile('./script.sh', ['arg1', 'arg2'], (error, stdout) => {
console.log(stdout);
});
fork - 创建 Node.js 子进程(支持进程间通信)
const { fork } = require('child_process');
const child = fork('./worker.js');
// 发送消息
child.send({ type: 'start', data: 'hello' });
// 接收消息
child.on('message', (msg) => {
console.log('收到子进程消息:', msg);
});
// worker.js
process.on('message', (msg) => {
console.log('收到父进程消息:', msg);
process.send({ type: 'response', data: 'world' });
});
67. cluster 模块
定义 cluster 模块用于创建共享服务器端口的子进程(工作进程),充分利用多核 CPU。
const cluster = require('cluster');
const http = require('http');
const os = require('os');
const numCPUs = os.cpus().length;
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 启动`);
// 创建工作进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 退出`);
cluster.fork(); // 重启
});
} else {
// 工作进程
http.createServer((req, res) => {
res.writeHead(200);
res.end(`工作进程 ${process.pid} 处理请求`);
}).listen(3000);
console.log(`工作进程 ${process.pid} 启动`);
}
68. worker_threads
定义 worker_threads 模块用于在 Node.js 中创建真正的多线程,适合 CPU 密集型任务。
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// 主线程
const worker = new Worker(__filename, {
workerData: { n: 40 }
});
worker.on('message', (result) => {
console.log('计算结果:', result);
});
worker.on('error', (err) => console.error(err));
} else {
// 工作线程
function fibonacci(n) {
return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
const result = fibonacci(workerData.n);
parentPort.postMessage(result);
}
69. 进程通信
| 方式 | 适用场景 | 示例 |
|---|---|---|
process.send() | fork 创建的子进程 | child.send() / process.on('message') |
| IPC 通道 | 子进程间通信 | 共享管道 |
| 共享内存 | worker_threads | SharedArrayBuffer |
| 消息传递 | worker_threads | parentPort.postMessage() |
| 信号 | 进程控制 | process.kill(pid, 'SIGTERM') |
| 文件/数据库 | 任意进程 | 通过文件系统 |
70. 守护进程
实现方式
- 使用第三方工具: PM2、forever
- 使用 systemd: Linux 系统服务
- Docker: 容器化部署
PM2 使用
# 启动应用
pm2 start app.js --name my-app
# 集群模式
pm2 start app.js -i max
# 查看状态
pm2 status
# 监控
pm2 monit
# 重启
pm2 restart my-app
# 保存进程列表
pm2 save
# 生成启动脚本
pm2 startup
十一、性能优化与调试
71. Node.js 性能优化
优化策略
| 策略 | 说明 | 实施方法 |
|---|---|---|
| 缓存 | 减少重复计算 | Redis、内存缓存 |
| 连接池 | 复用数据库连接 | mysql2 pool |
| 异步处理 | 非阻塞 I/O | Promise/async-await |
| 集群 | 多核利用 | cluster 模块 |
| 压缩 | 减少传输大小 | gzip、brotli |
| 流式处理 | 减少内存占用 | Stream API |
| 限流 | 防止过载 | rate limiting |
代码示例
// 使用 gzip 压缩
const compression = require('compression');
app.use(compression());
// 使用连接池
const pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
database: 'mydb'
});
72. 内存管理
V8 内存限制
- 64 位系统:约 1.4GB
- 32 位系统:约 0.7GB
- 可通过
--max-old-space-size调整
node --max-old-space-size=4096 app.js # 4GB
内存分区
V8 内存
├── New Space(新生代): 新分配的对象,快速 GC
├── Old Space(老生代): 长期存活的对象
├── Large Object Space: 大对象
├── Code Space: 编译后的代码
└── Map Space: 对象结构信息
73. 内存泄漏排查
常见原因
- 全局变量: 未声明的变量变成全局
- 闭包: 引用未释放
- 事件监听器: 未移除的监听器
- 缓存无限增长: 无限制的缓存
排查方法
// 方法 1:使用 Chrome DevTools
// 1. 启动: node --inspect app.js
// 2. 打开 chrome://inspect
// 3. Memory 面板拍照快照
// 方法 2:heapdump
const heapdump = require('heapdump');
setInterval(() => {
heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`);
}, 60000);
// 方法 3:node --heapsnapshot-signal
// node --heapsnapshot-signal=SIGUSR2 app.js
// kill -USR2 <pid>
74. CPU 分析
# 使用 clinic.js
npx clinic doctor -- node app.js
# 使用 0x
npx 0x app.js
# 使用 --prof
node --prof app.js
node --prof-process isolate-*.log > profile.txt
75. 调试方式
node inspect
node inspect app.js
Chrome DevTools
node --inspect app.js
# 打开 chrome://inspect 进行调试
debugger 关键字
function calculate(a, b) {
debugger; // 断点
return a + b;
}
console 调试
console.log('普通日志');
console.error('错误日志');
console.warn('警告日志');
console.time('timer');
// ... 代码 ...
console.timeEnd('timer');
console.table([{ name: 'Alice', age: 25 }]);
76. 错误处理
错误类型
// 1. 同步错误
try {
JSON.parse('invalid');
} catch (err) {
console.error(err);
}
// 2. 异步错误(回调)
fs.readFile('./nonexistent', (err, data) => {
if (err) {
console.error(err);
return;
}
});
// 3. Promise 错误
someAsync().catch(err => console.error(err));
全局错误捕获
// 未捕获的同步异常
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
// 记录日志、清理资源,然后退出
process.exit(1);
});
// 未处理的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
77. 日志管理 - Winston
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
logger.info('Server started');
logger.error('Database connection failed');
十二、实战应用
78. JWT 鉴权
定义 JWT(JSON Web Token)是一种用于身份验证的开放标准,由三部分组成:Header、Payload、Signature。
实现
const jwt = require('jsonwebtoken');
// 生成 Token
function generateToken(user) {
return jwt.sign(
{ id: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
}
// 验证中间件
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(403).json({ error: 'Token 无效' });
}
}
// 路由使用
app.post('/login', (req, res) => {
const user = { id: 1, role: 'admin' };
const token = generateToken(user);
res.json({ token });
});
app.get('/profile', authMiddleware, (req, res) => {
res.json({ user: req.user });
});
79. 文件上传
使用 multer
const multer = require('multer');
const path = require('path');
// 配置存储
const storage = multer.diskStorage({
destination: './uploads/',
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
}
});
// 文件过滤
const fileFilter = (req, file, cb) => {
const allowed = ['.jpg', '.jpeg', '.png', '.gif'];
const ext = path.extname(file.originalname).toLowerCase();
if (allowed.includes(ext)) {
cb(null, true);
} else {
cb(new Error('不支持的文件类型'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 5 * 1024 * 1024 } // 5MB
});
// 路由
app.post('/upload', upload.single('file'), (req, res) => {
res.json({
message: '上传成功',
file: req.file
});
});
// 多文件上传
app.post('/uploads', upload.array('files', 5), (req, res) => {
res.json({ files: req.files });
});
80. 分页功能设计
// 分页查询
app.get('/users', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;
const { count, rows } = await User.findAndCountAll({
limit,
offset,
order: [['createdAt', 'DESC']]
});
res.json({
data: rows,
pagination: {
total: count,
page,
limit,
totalPages: Math.ceil(count / limit),
hasNextPage: page < Math.ceil(count / limit),
hasPrevPage: page > 1
}
});
});
分页优化
-- 大数据量优化:游标分页
SELECT * FROM users
WHERE id < ?
ORDER BY id DESC
LIMIT 10;
附录:高频考点总结
事件循环执行顺序
console.log('1');
setTimeout(() => console.log('2'), 0);
setImmediate(() => console.log('3'));
process.nextTick(() => console.log('4'));
Promise.resolve().then(() => console.log('5'));
console.log('6');
// 输出:1 → 6 → 4 → 5 → 2/3(2和3顺序不确定,但在主模块中)