--- id: backend.nodejs.interview title: Node.js 面试题 type: interview domain: backend stack: [nodejs] tags: [Node.js, 事件循环, Express, NestJS, 面试] level: basic related: [backend.nodejs.express-nest] week: [14, 16] --- # Node.js 面试题 ## 基础 ### 【基础】Node 的事件循环是怎样的? **参考答案**:单线程执行 JS,I/O 由 libuv 异步完成。主要阶段:timers → pending callbacks → poll → check → close callbacks;每个回调执行完后先清空 `process.nextTick` 队列,再清空 Promise 微任务队列。 **追问**:`setTimeout(fn, 0)` 和 `setImmediate` 谁先执行?→ 在主模块中顺序不确定(取决于进入事件循环时计时器是否到期);在 I/O 回调中 `setImmediate` 一定先执行。 ### 【基础】Node 适合什么场景? **参考答案**:I/O 密集、高并发连接:BFF、API 网关、实时推送(WebSocket / SSE)、工具链。不适合长时间的 CPU 计算,否则阻塞事件循环;需要时用 `worker_threads` 或拆到其他服务。 **追问**:怎么发现事件循环被阻塞?→ 监控事件循环延迟(`perf_hooks.monitorEventLoopDelay`),用 CPU profile 找热点。 ### 【基础】Express 中间件的执行原理? **参考答案**:按注册顺序组成链,每个中间件处理后调用 `next()` 进入下一个,不调用则请求在此结束;错误处理中间件有 4 个参数,`next(err)` 或 Express 5 中 async 函数抛错时跳转过去。 **追问**:和 Koa 的洋葱模型有什么区别?→ Koa 的 `await next()` 可以在后续中间件执行完后继续执行后半段逻辑,天然适合计时和统一响应处理;Express 需要监听 `res` 的 `finish` 事件实现类似效果。 ## 进阶 ### 【进阶】NestJS 的依赖注入有什么好处? **参考答案**:类声明依赖,由容器创建和注入;便于替换实现(测试中注入假的服务)、统一生命周期管理、模块边界清晰;配合管道、守卫、拦截器形成统一规范。 **追问**:和 FastAPI 的 `Depends` 比较?→ 思想相同,FastAPI 基于函数参数声明,Nest 基于类构造函数和装饰器元数据。 ### 【进阶】Node 服务如何处理未捕获的异常? **参考答案**:请求级错误用统一错误处理中间件;进程级监听 `uncaughtException` / `unhandledRejection` 记录日志后退出,由进程管理器(容器编排 / systemd / PM2)重启;不要在未知状态下继续运行。 **追问**:为什么不能捕获后继续运行?→ 程序可能处于不一致的状态(连接、内存中的数据),继续运行可能产生更隐蔽的错误。 ### 【进阶】Node 如何利用多核? **参考答案**:多进程(`cluster` 或容器多副本 + 负载均衡)处理更多请求;`worker_threads` 处理 CPU 计算。服务要无状态,会话等放在 Redis。 **追问**:WebSocket 多实例怎么广播?→ 通过 Redis Pub/Sub 或消息队列在实例之间转发,负载均衡开启会话保持或使用支持多实例的方案。 ## 场景题 ### 【场景题】给 AI 应用做 BFF,你会做哪些事? **参考答案**:统一鉴权、限流、request id;首屏并行聚合与降级;SSE 流式透传、断开时中止上游、超时控制、上游错误以事件形式通知;保持薄层。详见[AI BFF 场景](scenarios/ai-bff.md)。 **追问**:怎么测试流式接口?→ 启动应用监听随机端口,用 `fetch` 读取响应,断言事件内容和顺序、错误事件和响应头,见[示例测试](examples/express-bff/app.test.ts)。 ### 【场景题】Node 服务内存持续上涨,怎么排查? **参考答案**:观察堆内存趋势;在不同时间点生成堆快照对比,找增长的对象和引用链;常见原因:全局缓存无上限、事件监听未移除、闭包持有大对象、定时器未清理。修复后压测验证。 **追问**:线上怎么安全地拿堆快照?→ 在摘除流量的单个实例上进行,快照期间进程会暂停,注意快照可能包含敏感数据。