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