Node.js 面试通常从事件循环和异步代码开始,然后延伸到 API、错误处理、流、性能和安全。候选人最常卡住的地方,是把事件循环说成某种魔法。面试官真正想听的,是你能清楚说明什么在哪儿运行、什么会阻塞。
下面是 24 道题及参考答案。代码示例都很短,方便你凭记忆手写出来。
事件循环与异步
1. 什么是 Node.js?
Node.js 是一个基于 Chrome V8 引擎、运行在浏览器之外的 JavaScript 运行时。它用单线程事件循环执行 JavaScript,配合非阻塞 I/O,因此特别擅长处理大量大部分时间都在等待的连接。
2. 事件循环是怎么工作的?
JavaScript 代码跑在单线程上。当它发起一个 I/O 操作,比如读文件或发请求,Node 会把它交给系统或线程池,然后继续往下走。操作完成后,它的回调会被排队,事件循环在调用栈为空时执行它。循环会依次经过几个阶段:timers、pending callbacks、poll(I/O)、check(setImmediate)和 close callbacks。
3. process.nextTick、Promise、setTimeout 和 setImmediate 的执行顺序是什么?
console.log('1 sync');
setTimeout(() => console.log('4 timeout'), 0);
setImmediate(() => console.log('5 immediate'));
Promise.resolve().then(() => console.log('3 promise'));
process.nextTick(() => console.log('2 nextTick'));
同步代码最先执行。然后是 process.nextTick 回调,接着是 promise 微任务,最后才进入事件循环的各个阶段。在主模块里,setTimeout(0) 和 setImmediate 的顺序可能不确定,但在 I/O 回调内部,setImmediate 总是先执行。要准备好解释原因,而不只是背结论。
4. 什么叫“阻塞事件循环”,怎么避免?
一个耗时的同步任务,比如解析超大 JSON、跑一个很大的循环,或者在请求处理函数里用 fs.readFileSync,会卡住所有其他请求,直到它执行完。避免的办法是使用异步 API、把任务拆成小块,或者把 CPU 密集型工作交给 worker 线程或另一个进程。
5. 回调、Promise 和 async/await 有什么区别?
回调是最早的风格,容易层层嵌套。Promise 表示一个未来的值,可以用 then 链式调用。async/await 是建立在 Promise 之上的语法糖,读起来像同步代码,并且可以用 try/catch 处理错误。
6. 怎么并行执行异步任务?
const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);
Promise.all 只要有一个失败就会 reject。Promise.allSettled 会等所有任务结束并报告每个结果。Promise.race 返回最先敲定的那个。如果调用之间相互独立,不要在循环里逐个 await。
7. worker 线程、子进程和 cluster 有什么区别?
worker 线程在同一个进程里用独立线程运行 JavaScript,可以共享内存,适合 CPU 密集型任务。子进程会启动独立的程序。cluster 模块会 fork 出多个应用副本,每个核心一个,共享同一个端口,这样就能利用所有 CPU 来处理请求。
模块与工具链
8. CommonJS 和 ES modules 有什么区别?
CommonJS 用 require 和 module.exports,同步加载。ES modules 用 import 和 export,是 JavaScript 的标准模块方案,还支持顶层 await。Node 两者都支持,具体用哪种由文件扩展名或 package.json 里的 type 字段决定。
9. package.json 里有什么?dependencies 和 devDependencies 有什么区别?
它保存元数据、脚本和依赖。dependencies 是运行时需要的,devDependencies 只在开发和测试时需要。lock 文件记录精确版本,保证每次安装结果一致。
10. 环境配置怎么处理?
用环境变量,通过 process.env 读取,再配一个经过校验的配置模块。不要把密钥提交到仓库里,生产环境要用密钥管理服务。
API 与 Express
11. Express 里的中间件是什么?
中间件是一个能访问请求对象、响应对象和 next 的函数。它按顺序执行,可以修改请求、结束响应,或者把控制权交给下一个中间件。常见用途包括日志、解析请求体、鉴权和错误处理。
app.use((req, res, next) => {
console.log(req.method, req.url);
next();
});
12. Express 应用里怎么处理错误?
在异步处理函数里捕获错误,传给 next(err),然后用一个带四个参数 (err, req, res, next) 的错误处理中间件返回统一的响应。在 Express 5 里,处理函数返回的 rejected promise 会自动转发。对于未捕获的异常和未处理的 rejection,应该记录日志并重启进程,而不是在状态未知的情况下继续运行。
13. 如果让你设计一个创建订单的 REST API 端点,你会怎么做?
校验输入、认证用户、检查权限、在数据库事务里创建订单、返回 201 以及新资源和它的位置,用幂等键处理重复请求,并返回带有合适状态码的清晰错误。
14. 用户认证怎么做?
可以用基于 cookie 的 session,也可以用 JWT 之类的 token。密码要用 bcrypt 或 argon2 这类慢哈希算法加密。用 JWT 时,过期时间要短,refresh token 要谨慎使用,不要把敏感数据放进 token 里。
流、文件与性能
15. 什么是流,为什么要使用流?
流以块为单位处理数据,而不是一次性将所有数据加载到内存中。流的类型包括可读流、可写流、双工流和转换流。在处理大文件、上传和下载时使用流。
const fs = require('node:fs');
const { pipeline } = require('node:stream/promises');
const zlib = require('node:zlib');
await pipeline(fs.createReadStream('in.log'), zlib.createGzip(), fs.createWriteStream('in.log.gz'));
16. 什么是背压?
当可写流处理速度跟不上可读流时,它会向数据源发出信号要求减速。pipeline 和 pipe 会为你处理这个问题。如果手动写入,请检查 write() 的返回值,并等待 drain 事件。
17. 如何发现内存泄漏?
持续观察内存变化,采集堆快照(Chrome DevTools 或 --inspect),然后对比快照找出哪些对象在持续增长。常见原因包括永不过期的全局缓存、未移除的事件监听器,以及持有大对象的闭包。
18. 如何对 Node.js 应用进行扩展?
使用 cluster 模块或进程管理器运行多个进程,将它们置于负载均衡器之后,通过将会话移至 Redis 或数据库来保持应用无状态,使用缓存,并用容器进行横向扩展。将 CPU 密集型工作交给 worker 或独立服务处理。
19. 如何对慢接口进行性能分析?
先用日志和计时进行测量,然后使用 --prof 或 --cpu-prof,或火焰图工具来找出热点函数。检查数据库查询,因为瓶颈往往出在那里,而不是 JavaScript 代码中。
安全与可靠性
20. Node.js 常见的安全问题有哪些?
注入攻击(SQL、NoSQL、命令注入)、跨站脚本攻击、认证薄弱、依赖项漏洞、代码中的密钥泄露、缺少速率限制,以及不安全的反序列化。通过输入验证、参数化查询、helmet、依赖审计(npm audit)和最小权限原则来缓解这些问题。
21. 如何实现优雅关闭?
收到 SIGTERM 信号后,停止接受新连接,让正在处理的请求完成,关闭数据库连接,然后退出,并设置超时作为兜底方案。
process.on('SIGTERM', () => {
server.close(() => process.exit(0));
});
22. 如何测试 Node.js 代码?
使用内置的 node:test、Jest 或 Vitest 等测试运行器进行单元测试,对外部调用进行 mock。针对测试数据库进行集成测试,使用 Supertest 等工具进行 HTTP 层面的测试。保持测试快速且相互独立。
设计与场景
23. 你的 API 在高负载下变慢,如何排查?
检查延迟和 CPU 的监控指标。如果某个核心的 CPU 很高,查找阻塞代码或繁重的 JSON 处理。如果 CPU 不高但延迟很高,则关注数据库或其他服务的等待时间。然后添加缓存、索引、连接池或批处理,再在负载下重新测试。
24. 如何为大文件构建上传接口?
将上传直接流式传输到存储,例如使用预签名 URL 的对象存储,而不是在内存中缓冲。设置大小限制、验证文件类型、按需扫描,之后在后台任务中处理。
如何准备
编写一个小型 API,包含认证、数据库和上传路由,然后用负载工具对其进行性能分析并修复一个问题。练习画出事件循环,并解释像上面这样的小脚本的输出。准备一个关于调试性能或可靠性问题的真实经历。对于编程环节,请参阅我们的编程面试助手指南,对于设计环节,请参阅系统设计指南。
大声说出你的答案,并阅读文字记录,找出你使用了某个术语却没有解释的地方。在任何现场面试中,遵守雇主关于 AI 工具的规定。
用 YesToTheOffer 练习 Node.js 面试答案
进行一次模拟技术面试,大声解释事件循环,然后回顾你的文字记录。30 分钟免费,无需订阅。



