Skip to content

知识点

1. Node.js 中的事件循环是如何工作的?

Node.js 的事件循环是单线程的,它负责调度和处理异步操作。事件循环的运行机制大致可拆分为以下几个阶段:

  • Timers (计时器阶段):执行 setTimeoutsetInterval 的回调。
  • 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 提供了多种优化性能的方法,包括:

  1. 异步编程 :使用异步操作如 Promiseasync/awaitstream 来防止阻塞事件循环。
  2. 代码分解 :避免代码逻辑过于复杂,通过模块化拆分大型代码块。
  3. 负载均衡 :使用 cluster 模块或负载均衡工具(如 Nginx)来分发请求到多个 worker。
  4. 缓存机制 :使用缓存(如 Redis、Memcached)来降低数据库查询频率。
  5. 避免内存泄漏 :定期监控和调试内存泄漏,使用 process.memoryUsage() 和工具如 heapdump
  6. 使用流 :处理大文件时,利用流(Streams)逐块读取或写入,替代一次性加载。
  7. 性能监控工具 :使用 clinic.jsnode --prof 或其他 APM 工具(如 New Relic)分析瓶颈。

3. Node.js 中流(Streams)的作用是什么?

流(Streams)是在 Node.js 中处理连续数据流的抽象。它们适用于处理无法一次性装载到内存中的大数据。流的类型包括:

  1. 可读流 (Readable) :从数据源读取数据(如 fs.createReadStream)。
  2. 可写流 (Writable) :将数据写入到某个目标(如 fs.createWriteStream)。
  3. 双工流 (Duplex) :既可读又可写(如 net.Socket)。
  4. 转换流 (Transform) :作为数据的处理和修改器(如 zlib.createGzip)。

用途示例: 在读取大文件时,可以使用流逐块读取,而不是一次性加载,提高内存利用率和程序效率。


4. 如何处理 Node.js 中未捕获的异常?

未捕获的异常可能导致程序崩溃。可以通过以下方式处理:

  1. 监听 processuncaughtException 事件:

javascript <>javascriptprocess.on('uncaughtException', (err) =\> { console.error('Unhandled Exception:', err); process.exit(1); // 通常退出进程 });

注意:虽然可以捕获未处理异常,但通常建议在捕获后记录日志并安全退出,因为可能导致应用状态不一致。

  1. 监听 processunhandledRejection 事件: 用于捕获未处理的 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 注入的最佳实践:

  1. 使用参数化查询 : 使用数据库库(如 mysql2pg)支持的安全 API:
   <>javascriptconst query = 'SELECT * FROM users WHERE id = ?';
   connection.query(query, [userId], (err, results) =\> {
       if (err) throw err;
       console.log(results);
   });
  1. ORM 框架 : 使用 ORM(如 Sequelize、TypeORM)自动验证和构建安全的 SQL 查询。
  2. 对输入进行验证和转义 : 验证用户输入是否合法,通过 validator 库检查输入并转义特殊字符。

8. 如何调试 Node.js 应用?

Node.js 提供了多种调试方式:

  1. 内置调试器 : 使用 node inspect 启动调试模式:
   node inspect app.js

使用 Node.js 提供的命令(如 n, c)逐步调试代码。

  1. 断点调试 : 使用 debugger 语句:
   <>javascriptdebugger;

启动程序会自动暂停在断点处。

  1. 第三方工具

  2. 使用 Chrome DevTools 进行调试。

  3. 安装 VS Code 插件 Debugger for Node.js,设置断点并调试。

  4. 日志工具 : 使用日志工具如 winstonpino,记录关键日志用于定位问题。


9. Node.js 中常用的设计模式有哪些?

Node.js 广泛使用以下设计模式:

  1. 单例模式 :只允许创建某个类的唯一实例。
   <>javascriptclass Singleton {
       constructor() {
           if (!Singleton.instance) {
               Singleton.instance = this;
           }
           return Singleton.instance;
       }
   }
   const instance = new Singleton();
   Object.freeze(instance);
  1. 观察者模式 :配合 EventEmitter 使用:
   <>javascriptconst EventEmitter = require('events');
   const emitter = new EventEmitter();
   emitter.on('event', () =\> console.log('Event occurred'));
   emitter.emit('event');
  1. 工厂模式 :动态创建对象。
   <>javascriptclass UserFactory {
       createUser(role) {
           switch (role) {
               case 'admin': return new Admin();
               case 'member': return new Member();
           }
       }
   }
  1. 中间件模式 :常见于 Express。
   <>javascriptapp.use((req, res, next) =\> { console.log('Middleware'); next(); });

10. 如何管理长时间运行的 Node.js 应用?

推荐使用以下工具和技术:

  1. 使用 pm2 一个常用的进程管理工具:

bash pm2 start app.js

它支持应用重启、日志分析和负载均衡。 2. 日志记录 :集成日志工具如 winston,监控应用日志。 3. 健康检查 :通过 HTTP 服务暴露健康检查端点,确保应用正常运行。 4. 内存管理 :通过 heapdumpprocess.memoryUsage() 检测内存泄漏。 5. 监控工具 :结合 APM 工具(如 New Relic、Prometheus + Grafana),实时监控性能和资源使用情况。

11. 如何实现 Node.js 应用的重载(Hot Reloading)?

热重载指的是在不停止应用的情况下重新加载代码变更。以下是几种实现热重载的方法:

  1. 使用 nodemon nodemon 是一个工具,它会自动监听代码变化并重启应用。 bash npm install -g nodemon nodemon app.js
  2. 动态重新加载模块: 通过 require 加载模块执行 delete require.cache,清除缓存并重新加载模块。 javascript <>javascriptdelete require.cache[require.resolve('./module.js')]; const module = require('./module.js');
  3. 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 模块系统来加载和运行模块。加载模块的机制分为以下几个步骤:

  1. 路径解析

  2. 如果是核心模块(如 fs):直接解析。

  3. 如果是相对路径(./module.js):解析为具体文件。
  4. 如果是包名(my-lib):从 node_modules 目录加载。

  5. 文件定位 : Node.js 尝试按以下顺序找到对应的模块:

  6. *.js

  7. *.json 文件会被解析为 JSON 对象。
  8. *.node 文件是用 C++ 编写的编译扩展。
  9. 如果是目录,寻找 package.jsonmain 字段或 index.js

  10. 模块缓存 : 每个模块首次被加载后会缓存到 require.cache 中,重复加载直接返回缓存。

  11. 模块包装 : 每个模块(文件)实际被包装成函数执行: