Node.js 面试题:EventLoop、Buffer、Stream、网络与多线程
Node.js 对前端来说是工程化、服务端渲染、BFF 和全栈能力的关键补充。面试重点是运行机制和服务稳定性。
运行机制
EventLoop
知识点讲解
Node 的事件循环由 libuv 驱动,包含 timers、pending callbacks、idle/prepare、poll、check、close callbacks 等阶段。setTimeout 在 timers 阶段,setImmediate 在 check 阶段,Promise 微任务会在当前阶段结束后尽快清空。
面试常问
问:setTimeout 和 setImmediate 谁先执行?
答:不一定。顶层代码里受定时器精度和事件循环进入时机影响;如果在 I/O 回调里,setImmediate 通常先于 setTimeout。
Buffer
知识点讲解
Buffer 用来处理二进制数据。Node 在网络、文件、加密场景中经常需要直接操作字节,字符串只是按编码解释后的结果。
面试常问
问:Buffer 和字符串有什么区别?
答:Buffer 是字节序列,字符串是字符序列。一个字符可能对应多个字节,比如 UTF-8 中文通常占 3 个字节。
Stream
知识点讲解
Stream 用分块方式处理数据,避免一次性把大文件读入内存。常见类型有 Readable、Writable、Duplex、Transform。背压机制能避免写入方过快导致内存膨胀。
面试常问
问:为什么大文件上传下载要用 Stream?
答:可以边读边写,降低内存占用,并通过背压协调读写速度,提升稳定性。
网络服务
HTTP Server
知识点讲解
Node 内置 http 模块可以创建服务。框架如 Express、Koa、NestJS 封装了路由、中间件、异常处理和生态能力。
面试常问
问:Koa 洋葱模型是什么?
答:中间件按顺序进入,遇到 await next() 后进入下一个中间件,后续中间件完成后再回到上一层继续执行,形成类似洋葱的前后置处理。
WebSocket、GraphQL 与 gRPC
知识点讲解
WebSocket 适合实时双向通信。GraphQL 让客户端声明需要的数据结构,适合复杂聚合查询。gRPC 基于 HTTP/2 和 Protobuf,适合服务间高性能通信。
面试常问
问:WebSocket 和 HTTP 轮询区别?
答:轮询是客户端定时请求,实时性和资源利用都较差;WebSocket 建立长连接后可双向推送,适合聊天、协作、实时通知。
并发与稳定性
Cluster
知识点讲解
Node 单进程单主线程,CPU 密集任务会阻塞事件循环。Cluster 可以启动多个进程利用多核 CPU,通常由主进程管理 worker。
面试常问
问:Node 适合 CPU 密集任务吗?
答:不适合放在主线程直接跑。可以用 Worker Threads、子进程、任务队列,或者交给专门服务处理。
Worker Threads
知识点讲解
Worker Threads 提供线程级并行能力,适合 CPU 密集计算。它和主线程之间通过消息通信,也可以使用 SharedArrayBuffer 共享内存。
面试常问
问:Worker Threads 和 Cluster 区别?
答:Cluster 是多进程,适合扩展服务吞吐;Worker Threads 是多线程,适合分担 CPU 密集计算。
错误处理与日志
知识点讲解
服务端要处理同步错误、异步 rejection、接口异常、超时、重试和日志追踪。生产环境不能只靠 console.log,需要结构化日志、请求 ID、监控告警。
面试常问
问:Node 服务如何防止崩溃?
答:业务层做好 try/catch 和统一错误处理中间件,处理未捕获异常并上报,进程异常后由 PM2、容器或守护进程拉起,同时保留日志和告警。
底层追问与代码示例
Node 事件循环阶段细节
知识点讲解
Node 的 process.nextTick 不属于普通微任务队列,它优先级高于 Promise 微任务。滥用 nextTick 递归会饿死 I/O。
1 | setTimeout(() => console.log('timer'), 0) |
常见输出:
1 | sync |
面试常问
问:为什么不要大量递归调用 process.nextTick?
答:它会在事件循环进入下一阶段前持续清空,可能导致 timers、I/O、check 阶段迟迟无法执行。
Stream 背压代码示例
知识点讲解
write() 返回 false 表示写入缓冲区已满,读取方应该暂停,等 drain 再继续。
1 | const fs = require('fs') |
更推荐使用 pipeline,它会处理背压和错误传播:
1 | const { pipeline } = require('stream/promises') |
CPU 密集任务放到 Worker
知识点讲解
CPU 密集任务会阻塞主线程,让所有请求变慢。Worker Threads 可以把计算移出主线程。
1 | // main.js |
1 | // worker.js |
面试常问
问:Node 高并发靠什么?
答:靠事件驱动和非阻塞 I/O 处理大量 I/O 并发,不是靠单线程执行 CPU 任务很快。CPU 密集计算仍要拆出去。