Node.js 面试题
Node.js 面试题
React、Next.js、Node.js 各分入门 / 中等 / 高难,每档 20 题,共 180 题,★ 是高频。06 用来快速入门,本篇用来系统复习。通用方案不代表项目已有实现。
Node.js 入门(20 题)
1. ★ Node.js 是什么?
基于 V8 的 JavaScript 运行时,提供网络、文件、进程等服务端能力。Node 不是框架,Express 是其上的 Web 框架;浏览器 DOM API 不能直接当成 Node 内置能力。
2. ★ Node 是单线程吗?
普通 JS 回调主要在一个事件循环线程执行,但运行时还有系统异步 IO、libuv 线程池和可选 worker_threads。说「所有操作都单线程」不准确;CPU 重循环仍会挡住主线程。
3. 事件循环负责什么?
调度不同阶段的回调与任务,避免一个请求等网络时阻塞所有请求。JS 回调自身要尽快返回;异步 API 不代表回调中的 JSON 解析、正则或加密也不会阻塞。
4. ★ Promise 和 async/await 的关系?
async 函数返回 Promise,await 用较直观的形式组合异步步骤。await 只暂停该异步函数,不阻塞线程等待网络;但 await 后执行的重 CPU 逻辑照样会占住事件循环。
5. 回调、Promise、事件适合什么?
回调可表示一次完成,Promise 表示一次最终成功/失败,事件/流适合多次结果。不要把无限事件强装成一个不断 resolve 的 Promise;监听器要清理并处理 error。
6. ★ CommonJS 与 ESM 区别?
CommonJS 用 require/module.exports,ESM 用 import/export,加载、互操作和路径规则不同。项目 package.json 的 type 与扩展名决定解释方式;写脚本时明确 .mjs/.cjs,别混用后猜执行顺序。
7. package.json 与 lockfile 有什么区别?
前者声明脚本和依赖范围,锁文件记录实际解析版本与完整性。可重复安装应提交匹配的锁文件并用对应包管理器,不能只固定顶层版本却让传递依赖每次变化。
8. ★ dependencies 和 devDependencies 怎么分?
运行所需放 dependencies,构建/检查工具通常放 devDependencies。若部署在运行时编译或执行某工具,它也成运行依赖;多阶段镜像应把编译产物及运行所需库一起复制。
9. Buffer 是什么?
表示字节序列,适合文件、网络和密码学处理。字符串字符数不等于 UTF-8 字节数;接收网络分块时一个中文字符可能跨块,不能每块各自无状态地转字符串。
10. ★ Stream 为什么适合大文件?
逐块处理,减少一次性加载整个文件的内存。仍需处理错误、关闭和背压;把所有 chunk 先收进数组最后 concat,最终仍会占全量内存,不能算真正流式处理。
11. Express middleware 怎么工作?
按注册顺序处理 req/res,调用 next 交给后续,或发送响应结束。顺序决定 body 解析、认证和路由能看到什么;忘记 next 或响应可能让请求悬挂,重复响应会报 headers sent。
12. ★ Express 错误处理怎么写?
统一错误 middleware 接收 err、req、res、next,已发头时交给合适的后续处理。异步错误传播与 Express 大版本有关,按项目版本处理;不要把每个异常原文直接返回给客户端。
13. JSON body 为什么需要大小限制?
解析前请求体就可能占内存,大对象 JSON.parse 也会占 CPU。给 body、上传和解压分别设预算,超限早返回;限量不替代字段校验,合法 JSON 也可能不符合业务要求。
14. ★ process.env 的值是什么类型?
读取到的通常是字符串或 undefined,'false' 作为字符串仍是真值。启动时显式解析布尔、数字、URL,校验范围和必填项;不要等第一个请求才发现端口或密钥格式错误。
15. Node 怎么读写文件?
可用 fs/promises 做异步操作,大文件用 stream。文件路径由服务端限定根目录并规范化,防路径穿越;写入还要处理目录权限、临时文件与原子替换,不能直接信用户文件名。
16. ★ setTimeout(fn, 0) 是立刻执行吗?
不是,只是达到最小延迟后有资格被调度,还要等当前 JS 和相关队列。事件循环被重任务堵住时会晚很多;超时需要看实际墙钟截止时间,不能靠定时器次数算经过时间。
17. npm script 为什么比全局装工具好?
脚本优先使用项目依赖的可执行文件,版本随锁文件管理,团队更一致。安装脚本也会执行代码,要了解依赖来源;Node 版本和包管理器版本也应记录。
18. ★ Node 服务如何接收停止信号?
监听 SIGTERM/SIGINT,先停止接新流量,再等待有限时间处理在途请求,关闭连接和资源。不要在信号回调里立刻 process.exit,否则日志、响应和落库可能来不及完成。
19. Node 可以做 CPU 密集任务吗?
能,但不应让重任务长时间占主线程。可用 worker_threads、独立进程或后台任务服务,控制并发与内存;线程启动和数据传输有成本,小计算不必都创建 worker。
20. ★ 项目 Node 服务能怎么讲?(项目)
xknow-payment 用 Express 提供扫码收款页面和 API,微信通知单独读取 raw body 后验签。讲清请求解析顺序、超时和错误处理即可;它不是整个 AI Agent 后端的实现语言。
Node.js 中等(20 题)
1. ★ nextTick、Promise 微任务、timer 的顺序?
都在当前同步代码之后,但 nextTick 与 Promise 的相对顺序需说明 CJS/ESM、执行上下文和 Node 版本。I/O 回调内与顶层 timer/immediate 也有差别,别背一条适用于所有情况的固定序列。
2. setImmediate 和 setTimeout(0) 谁先?
顶层顺序不能稳定依赖,受事件循环时点等影响;在常见 I/O 回调里 immediate 通常先进入对应 check 阶段。需要严格业务顺序应显式串联,不能把调度偶然性当同步协议。
3. ★ libuv 线程池处理什么?
部分文件操作、dns.lookup 和一些加密/压缩任务会用线程池,普通网络 socket 多依赖 OS 非阻塞机制。线程池饱和会拖慢看似无关操作;增大池要同时评估 CPU 与内存。
4. stream 背压怎么处理?
Writable.write 返回 false 表示应暂停继续写,等 drain 再继续;可优先用 pipeline 连接并传播错误。忽略背压会堆积缓冲,面对慢客户端时内存迅速增长。
5. ★ pipeline 比自己监听 data 有什么好处?
它统一连接流、处理背压并传播很多错误/销毁行为。仍需确认业务结束语义与 abort 支持,避免对 HTTP 响应销毁造成意外展示;特殊恢复逻辑要自己设计,不能无限重跑整个上传。
6. fetch 超时怎么做?
用 AbortSignal.timeout 或 AbortController,并覆盖整个需要保护的请求/读取阶段,结束后清理 timer。收到响应头不代表响应体已完整;业务还需区分取消、连接失败和结果未知。
7. ★ Promise.all 和 allSettled 怎么选?
all 在一个拒绝时整体拒绝,但不会自动取消其他已启动任务;allSettled 等全部完成,便于独立结果处理。无依赖请求可并行,有共享预算则加并发限制,不要一次启动十万条。
8. Express 4 与 5 的异步错误区别?
Express 5 会把返回 Promise 的处理器拒绝传到错误处理;Express 4 通常需显式 catch(next) 或包装器。两者都不自动捕获任意后台事件的错误;以实际依赖版本为准。
9. ★ 为什么支付验签要用原始 body?(项目)
签名针对原始字节/原文,先 JSON.parse 再 stringify 可能改变空白和字段顺序。项目先挂 notify 的 express.raw,再挂通用 express.json,保持验签输入;业务解析应在验签通过后进行。
10. 金额为什么用整数分?
避免二进制浮点的小数误差,接口明确单位并检查整数范围。乘 100 前仍要严格解析十进制输入,不能放任任意小数四舍五入;大整数要考虑 JS Number 的安全整数上限。
11. ★ trust proxy 是干什么的?(项目)
让框架按可信代理规则解释 X-Forwarded-*,影响客户端 IP、协议和安全判断。项目设置为 1,含义依赖部署跳数;若入口路径不同,攻击者可能伪造头,不能无条件信任所有来源。
12. HTTP keep-alive 如何影响性能?
复用连接减少握手,但连接池有空闲超时、最大连接数和对端关闭边界。配好请求总 deadline 与重试;遇到复用连接偶发 reset,要区分可重试读取和不可重复的写操作。
13. worker_threads 与 child_process 怎么选?
worker 适合同进程的并行 JS 计算,可按协议传输或共享部分内存;子进程隔离更强,可跑其他程序。两者都要有任务池、超时和回收,别每个请求无界启动一个。
14. ★ spawn 和 exec 的区别?
spawn 以流处理输出,exec 常经 shell 并缓存输出;不可信输入拼 shell 容易命令注入。优先明确命令与参数数组,限制输出/时间/目录和环境;shell:false 也不代替目标程序参数校验。
15. EventEmitter 泄漏怎么看?
监听器数量持续增长、重复响应、堆里对象无法释放是线索。once/移除 listener 要覆盖结束和错误路径;提高 maxListeners 只能调告警阈值,不能修复忘记清理。
16. ★ AsyncLocalStorage 有什么用?
在异步调用链传 trace_id 等请求上下文,避免全局变量串请求。第三方回调与自建异步边界需验证传播;不能把它当跨进程共享状态,worker 和外部任务要显式传关联信息。
17. Web Streams 和 Node Streams 有什么不同?
接口、读取方式和背压协议不同,fetch 的 body 常是 Web ReadableStream。按 API 转换并检查取消传播,别把 getReader 与 on('data') 混用;转换也不意味着丢失错误和关闭责任。
18. ★ 大文件哈希为什么要边读边算?
避免 readFile 把文件全放内存;流里逐块更新 hash,结束才取结果。CPU 消耗仍存在,大量并行哈希要限制并发;还要处理读错误,不能对不完整内容返回成功摘要。
19. 服务如何做输入校验?
对路径、query、body、header 分别校验类型、长度、枚举和业务归属;拒绝额外危险字段。JSON 解析只证明语法合法;校验完成后的数据也不能拿来绕过资源级权限检查。
20. ★ Node 的可观测性看什么?
请求量、错误率、延迟、事件循环延迟、堆/RSS、GC、连接池和下游耗时。排队与执行分开,日志带请求 ID;CPU 不高但延迟大也可能是线程池、外部 IO 或连接池等待。
Node.js 高难(20 题)
1. ★ CPU 100%,所有请求一起变慢怎么排?
采 CPU profile 看重循环、JSON、正则、序列化或压缩,再查事件循环延迟。对大任务限输入、分片或交给 worker 池;先限流止血,不能把函数改 async 就认为 CPU 工作变异步。
2. CPU 不高但文件读取很慢?
检查 libuv 线程池是否被密码学/压缩占满、磁盘延迟、并发和网络文件系统。用受控样本区分排队和实际执行;提高 UV_THREADPOOL_SIZE 前评估主机核数,避免从排队变成 CPU 抢占。
3. ★ 服务内存增长但 heap 稳定?
看 Buffer、ArrayBuffer、原生库、线程、子进程和 RSS 碎片;堆快照只解释 JS 堆的一部分。检查流缓冲和未消费响应体,按对象/连接生命周期复现,不单纯调 V8 old space。
4. 大 JSON 请求怎样拖垮事件循环?
读 body 占内存,parse 和 stringify 是同步 CPU 工作,嵌套/字段量也有成本。限制字节、深度与返回量,必要时改变协议或隔离处理;不能只用异步 readFile 就消除解析阻塞。
5. ★ SSE 代理怎样支持慢客户端?
读取上游与写下游联动背压,限制单连接缓冲,客户端断开要取消上游。定期心跳、总/空闲超时和连接配额分别设计;别无限缓存模型输出直到用户网速恢复。
6. 微信支付通知来了三次怎么办?
先验签并核对金额、商户与订单,再用订单/通知业务键幂等落库,返回已处理结果。不要把通知 ID 存一个无界进程 Set 当可靠防重;进程重启和多副本都会失效。
7. ★ 调支付接口超时能立刻重下单吗?
超时表示结果未知,先按相同订单号查询或使用对方支持的幂等规则恢复。不能用新订单号盲重试;明确状态机、对账与恢复窗口。这是设计原则,不据此断言项目已实现完整对账系统。
8. Node 优雅停机怎样覆盖长连接?
先 readiness 摘流,停止接受新连接,跟踪 HTTP/SSE/WebSocket 与后台工作;有限期结束后主动关闭残留并记录未完成业务。具体 close API 行为随 Node 版本变化,验证真实连接而非只测空闲服务。
9. ★ 服务发了响应,异步处理却失败怎么补偿?
如果工作必须可靠,响应前先持久化业务意图,再由队列/outbox 执行;后台 Promise 不能保证进程重启后继续。日志能帮助发现失败,但不构成可靠任务存储和恢复机制。
10. unhandledRejection 能捕获后继续运行吗?
先修复缺失的错误链路,按 Node 版本与启动策略处理未处理拒绝。uncaughtException 后进程状态可能不可信,通常记录并由监管重启;全局吞异常长期运行会掩盖部分写入和资源泄漏。
11. 正则为什么可能导致拒绝服务?
有些模式对恶意输入出现灾难性回溯,占住事件循环。限制长度、改写模式或用合适的线性匹配工具,复杂校验设资源边界;「只是匹配一个字符串」也可能是昂贵计算。
12. ★ 多进程全局限流怎么做?
进程内 Map 只能管本进程,跨实例用原子共享存储或统一入口。定义用户/组织/接口维度、突发额度和故障策略;计数与过期要原子,客户端 IP 还需可信代理规则。
13. 为什么不能无界 Promise.all 访问数据库?
所有任务立即启动,连接池排队、内存和下游 QPS 同时增长。用并发池、分批与取消预算,记录排队时间;请求超时后未取消的任务仍可能执行,要控制副作用和资源回收。
14. 线程池任务结果如何防串单?
每个任务有唯一 ID、输入版本和目标身份,worker 回包先匹配,超时/取消后的旧结果不能更新新任务。协议覆盖崩溃与重启,池限制在途数量;共享可变内存要有同步机制。
15. ★ Node 服务如何避免 SSRF?
外部 URL 限协议、域名/端口和跳转,解析后校验私网/本地/元数据地址并处理 DNS 重绑定风险。不要把任意 fetch 封成公共接口;请求预算、响应大小和凭证转发也要限制。
16. 上传接口如何防目录穿越与解压炸弹?
使用服务端生成的文件名和受控目录,校验规范化/符号链接后的目标;限制总解压大小、文件数、嵌套和时间。原始压缩包小不表示解压成本小,失败要清理临时产物。
17. 金额计算超过 Number 安全范围怎么办?
用 BigInt 或可靠十进制库,接口明确字符串/整数单位与舍入规则。BigInt 不能直接默认 JSON.stringify,序列化协议需双方约定;禁止在中途转回 Number 重新引入精度损失。
18. ★ 如何做依赖供应链的基本控制?
固定运行时和锁文件,用可重复安装,审查新增依赖与安装脚本,保持漏洞修复和构建可追溯。扫描结果需按实际可达代码评估,避免盲升破坏协议;密钥不进入包、镜像层或日志。
19. 如何验证 Node 的事件循环理解?
分别在明确 CJS、ESM、I/O 回调内写小实验,打印同步、微任务、nextTick、timer 和 immediate。断言有保证的先后关系,不断言顶层 timer/immediate 的偶然顺序;本篇附自检脚本。
20. ★ 全栈面试如何讲 Node 项目边界?(项目)
讲收款服务的 Express 路由、raw body 验签、整数金额和外部请求超时;Agent 主后端仍是 Python。关于持久化幂等、多副本和对账,先查实际实现,缺失就作为改进方案,不说已经上线。
可运行练习
verify_18.mjs演示函数式 reducer、终态防回退、CJS/ESM 调度差异,以及正确的 UTF-8 分块解码。它是纯 Node 自检,不冒充 React DOM、Next SSR 或浏览器 SSE 的端到端测试。具体结果见 验证记录.md。
项目事实与版本
2026-10-21 核对各仓库 package.json、next.config.ts 和实际源码:
| 项目 | 能确认的内容 |
|---|---|
../xknow-frontend/ | Next 15.3.6、React 19.1.0、antd 5.26.0、Redux Toolkit 2.6.0、SCSS;页面/组件→store/services→utils 分层,见 AGENTS.md、docs/ARCHITECTURE.md |
../xknow-console/ | Next 16.2.9、React 19.2.7、Tailwind 4,output=standalone |
../xknow-gateway/ | Next 16.2.9、React 19.2.7、Tailwind 4,output=standalone |
| 三个前端的 rewrite | frontend 有 /api/web、/api/ai、/api/ai_task 等规则;console 与 gateway 仅在分别配置 CONSOLE_API_PROXY_TARGET / GATEWAY_API_PROXY_TARGET 时启用对应规则,不能概括成所有部署都由 Next 转发全部 /api |
../xknow-payment/ | Node + Express 扫码收款服务;src/server.js 先挂载 notify 的 raw body 路由,再挂全局 express.json;src/wechatpay.js 含签名验证和外部请求超时 |
官方核对入口:React Effect、React use、Next 15 升级说明、Next 16 升级说明、Server / Client Components、Node 事件循环、Node Stream。Next 缓存和异步请求 API 有版本差异,回答时先报项目版本。
第一部分:React
四、Node.js
1. Node.js 是什么?
基于 Chrome V8 引擎的 JavaScript 服务端运行时,让 JS 能在浏览器外运行(读写文件、起 HTTP 服务)。
2. Node 为什么单线程还能高并发?
- JS 代码在一个主线程上跑,但 IO(网络、文件)交给底层 libuv,由操作系统的异步机制(epoll / kqueue)或线程池完成,完成后通过事件循环回调。
- 所以 Node 适合 IO 密集(API 网关、BFF、实时推送);不适合 CPU 密集(一段计算会堵住所有请求)。
- 对比 Python gevent:思路一样(单线程 + 事件循环 + 非阻塞 IO),区别是 Node 的整个生态天生就是异步的,不需要 monkey-patch。
3. Node 的事件循环有哪几个阶段?(中等题)
timers(setTimeout 回调)→ pending callbacks → poll(等待 IO)→ check(setImmediate)→ close callbacks。每个阶段之间清空微任务;process.nextTick 比 Promise 微任务还优先。
4. CPU 密集的任务怎么办?
worker_threads(多线程)、child_process(子进程)、cluster 模块 / PM2 多进程(利用多核)。产品前端旧的裸机部署方式就是 pm2 守护。
5. CommonJS 和 ES Module?
- CommonJS:
require/module.exports,同步加载,Node 传统写法。 - ESM:
import/export,静态分析、支持 tree-shaking,浏览器和 Node 都支持,是标准。package.json里"type": "module"让.js按 ESM 处理。
6. npm、yarn、pnpm 区别?为什么用 pnpm?
pnpm 用全局内容寻址存储 + 硬链接,同一个包版本在磁盘上只存一份,装得快、省空间;而且依赖是严格的(没在 package.json 声明的包不能用,避免「幽灵依赖」)。我们所有前端统一 pnpm。
7. Express 中间件是什么?
app.use(express.json()); // 解析 JSON 请求体
app.use('/admin', basicAuth); // 只对 /admin 生效的鉴权
app.get('/pay/:linkId', async (req, res) => { ... });
app.use((err, req, res, next) => { // 四个参数 = 错误处理中间件
res.status(500).json({ msg: err.message });
});中间件就是 (req, res, next) => {},按注册顺序依次执行,调用 next() 交给下一个。对比 Flask:相当于 before_request + 蓝图 + errorhandler;对比 Java:相当于 Servlet Filter 链(又是责任链模式)。 你的经历:微信 Native 扫码收款服务就是 Node + Express,有一个 Basic Auth 保护的管理后台。被问到可以讲:支付回调要验签、要幂等(微信会重复通知)、金额以服务端为准不信任前端。
8. Stream(流)是什么?
数据分块处理,不用一次性全读进内存。可读流、可写流、pipe 连接。用于大文件、代理转发、流式响应。
