知识点
事件循环
Node.js 的事件循环是单线程的,负责调度和处理异步操作。
执行阶段:
- Timers (计时器阶段):执行
setTimeout和setInterval的回调 - Pending Callbacks (待处理回调阶段):处理某些系统操作的回调,例如文件系统操作完成后的回调
- Idle, Prepare (空闲阶段):内部使用
- Poll (轮询阶段):获取新的 I/O 事件,执行与 I/O 相关的回调
- Check (检查阶段):执行
setImmediate回调 - Close Callbacks (关闭回调阶段):处理如
socket.on("close")等回调
微任务队列:
Promise 的回调会被加入到微任务队列(Microtask Queue),并且优于宏任务队列(如 setTimeout)。
性能优化
1. 异步编程
使用 Promise、async/await、stream 来防止阻塞事件循环
2. 代码分解 避免代码逻辑过于复杂,通过模块化拆分大型代码块
3. 负载均衡
使用 cluster 模块或负载均衡工具(如 Nginx)来分发请求到多个 worker
4. 缓存机制 使用缓存(如 Redis、Memcached)来降低数据库查询频率
5. 避免内存泄漏
定期监控和调试内存泄漏,使用 process.memoryUsage() 和工具如 heapdump
6. 使用流 处理大文件时,利用流(Streams)逐块读取或写入,替代一次性加载
7. 性能监控工具
使用 clinic.js、node --prof 或其他 APM 工具(如 New Relic)分析瓶颈
流(Streams)
流是在 Node.js 中处理连续数据流的抽象,适用于处理无法一次性装载到内存中的大数据。
流的类型:
- 可读流 (Readable):从数据源读取数据(如
fs.createReadStream) - 可写流 (Writable):将数据写入到某个目标(如
fs.createWriteStream) - 双工流 (Duplex):既可读又可写(如
net.Socket) - 转换流 (Transform):作为数据的处理和修改器(如
zlib.createGzip)
用途示例: 在读取大文件时,可以使用流逐块读取,而不是一次性加载,提高内存利用率和程序效率。
异常处理
1. 监听 process 的 uncaughtException 事件:
process.on('uncaughtException', (err) => {
console.error('Unhandled Exception:', err);
process.exit(1);
});
2. 监听 process 的 unhandledRejection 事件:
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
3. 推荐方式: 使用全局错误处理中间件(在 Express 等框架中)或 try-catch 捕获可能的错误。
敏感信息存储
1. 配置文件 (.env)
使用 .env 文件存储敏感信息,借助第三方库如 dotenv 加载环境变量:
require('dotenv').config();
const dbPassword = process.env.DB_PASSWORD;
2. 加密存储
加密敏感信息存储到数据库,使用 crypto 模块或外部库(如 bcrypt、argon2)
3. 环境变量 敏感信息如 API Key 应通过环境变量传递,避免硬编码
4. 限制权限 限制敏感文件的访问权限
5. 使用配置管理工具 如 HashiCorp Vault,或云上的机密管理系统(如 AWS Secrets Manager)
多线程
Node.js 通过 Worker Threads 模块提供了多线程功能,用于处理 CPU 密集型任务:
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js', { workerData: { num: 10 } });
worker.on('message', (result) => {
console.log('Result from worker:', result);
});
worker.on('error', (err) => {
console.error('Worker error:', err);
});
worker.on('exit', (code) => {
console.log('Worker exited with code:', code);
});
适用场景:
- 处理计算密集型任务(如图像处理、文件压缩)
- 避免阻塞主线程
SQL 注入防护
1. 使用参数化查询
使用数据库库(如 mysql2、pg)支持的安全 API:
const query = 'SELECT * FROM users WHERE id = ?';
connection.query(query, [userId], (err, results) => {
if (err) throw err;
console.log(results);
});
2. ORM 框架 使用 ORM(如 Sequelize、TypeORM)自动验证和构建安全的 SQL 查询
3. 输入验证和转义
验证用户输入是否合法,通过 validator 库检查输入并转义特殊字符
调试方法
1. 内置调试器
使用 node inspect 启动调试模式:
node inspect app.js
使用 Node.js 提供的命令(如 n, c)逐步调试代码
2. 断点调试
使用 debugger 语句:
debugger;
启动程序会自动暂停在断点处
3. 第三方工具
- 使用 Chrome DevTools 进行调试
- 安装 VS Code 插件
Debugger for Node.js,设置断点并调试
4. 日志工具
使用日志工具如 winston 或 pino,记录关键日志用于定位问题
设计模式
1. 单例模式 只允许创建某个类的唯一实例
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
}
const instance = new Singleton();
Object.freeze(instance);
2. 观察者模式 配合 EventEmitter 使用:
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('event', () => console.log('Event occurred'));
emitter.emit('event');
3. 工厂模式 动态创建对象
class UserFactory {
createUser(role) {
switch (role) {
case 'admin': return new Admin();
case 'member': return new Member();
}
}
}
4. 中间件模式 常见于 Express
app.use((req, res, next) => { console.log('Middleware'); next(); });
长时间运行应用管理
1. 使用 pm2
pm2 start app.js
支持应用重启、日志分析和负载均衡
2. 日志记录
集成日志工具如 winston,监控应用日志
3. 健康检查 通过 HTTP 服务暴露健康检查端点,确保应用正常运行
4. 内存管理
通过 heapdump 和 process.memoryUsage() 检测内存泄漏
5. 监控工具 结合 APM 工具(如 New Relic、Prometheus + Grafana),实时监控性能和资源使用情况
热重载
1. 使用 nodemon
npm install -g nodemon
nodemon app.js
2. 动态重新加载模块
delete require.cache[require.resolve('./module.js')];
const module = require('./module.js');
3. Webpack 或 Babel 等工具结合热加载
const webpack = require('webpack');
const middleware = require('webpack-dev-middleware');
app.use(middleware(webpack(config)));
4. PM2 的 Watch 模式
pm2 start app.js --watch
模块加载机制
1. 路径解析
- 如果是核心模块(如
fs):直接解析 - 如果是相对路径(
./module.js):解析为具体文件 - 如果是包名(
my-lib):从node_modules目录加载
2. 文件定位 Node.js 尝试按以下顺序找到对应的模块:
*.js*.json文件会被解析为 JSON 对象*.node文件是用 C++ 编写的编译扩展- 如果是目录,寻找
package.json的main字段或index.js
3. 模块缓存
每个模块首次被加载后会缓存到 require.cache 中,重复加载直接返回缓存
4. 模块包装 每个模块(文件)实际被包装成函数执行