一、Node.js 是什么?
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。
一句话:让 JavaScript 跑在服务器端,不再只能写网页。
关键词:
- 不是语言,不是框架,是一个运行环境
- 单线程:主线程只有一个(但底层用 libuv 做异步 I/O)
- 事件驱动:通过事件循环处理并发
- 非阻塞 I/O:读文件、查数据库不会卡住主线程
二、Node.js 优缺点
优点
| 优点 | 说明 |
|---|---|
| 高并发 | 事件驱动 + 非阻塞 I/O,轻松处理大量并发连接 |
| 前后端统一 | 前后端都用 JS,降低切换成本 |
| NPM 生态 | 全球最大的包管理生态,开箱即用 |
| 轻量高效 | 适合 I/O 密集型应用(API 服务、实时通信) |
| 上手快 | 前端开发者可以快速转后端 |
缺点
| 缺点 | 说明 |
|---|---|
| 不适合 CPU 密集型 | 单线程,大量计算会阻塞主线程 |
| 回调地狱 | 早期大量回调嵌套(现在用 async/await 解决了) |
| 单线程不够稳 | 一个未捕获异常可能导致整个进程崩溃 |
| 弱类型 | JS 缺少类型检查(可用 TypeScript 弥补) |
面试回答模板
Node.js 适合 I/O 密集型(API网关、实时聊天、文件处理),不适合 CPU 密集型(视频编码、大量数学计算)。可以用 Worker Threads 或子进程来处理 CPU 密集任务。
三、事件循环机制(Event Loop)
这是 Node.js 面试的必考题。
为什么需要事件循环?
Node.js 是单线程的,但需要处理大量异步操作(读文件、网络请求、定时器)。事件循环就是它的"调度中心"。
事件循环的 6 个阶段
┌───────────────────────────┐
┌─→│ timers │ ← setTimeout / setInterval 回调
│ └───────────┬───────────────┘
│ ┌───────────┴───────────────┐
│ │ pending callbacks │ ← 系统级回调(TCP错误等)
│ └───────────┬───────────────┘
│ ┌───────────┴───────────────┐
│ │ idle, prepare │ ← 内部使用
│ └───────────┬───────────────┘
│ ┌───────────┴───────────────┐
│ │ poll │ ← I/O 回调(文件读写、网络请求)
│ └───────────┬───────────────┘
│ ┌───────────┴───────────────┐
│ │ check │ ← setImmediate 回调
│ └───────────┬───────────────┘
│ ┌───────────┴───────────────┐
│ │ close callbacks │ ← socket.on('close') 等
│ └───────────┬───────────────┘
└─────────────┘(循环)
各阶段做什么?
| 阶段 | 执行内容 |
|---|---|
| timers | 执行到期的 setTimeout / setInterval 回调 |
| pending | 执行上一轮延迟的 I/O 回调 |
| poll | 获取新的 I/O 事件,执行 I/O 回调。这是最重要的阶段 |
| check | 执行 setImmediate 回调 |
| close | 执行关闭事件回调,如 socket.on('close') |
微任务 vs 宏任务
在 Node.js 中,每个阶段切换之间都会清空微任务队列:
| 类型 | 包含 | 优先级 |
|---|---|---|
| 微任务 | process.nextTick、Promise.then | 高(每个阶段之间执行) |
| 宏任务 | setTimeout、setInterval、setImmediate、I/O | 低(在对应阶段执行) |
执行顺序:process.nextTick > Promise.then > 宏任务
经典面试题
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
setImmediate(() => {
console.log('3');
});
Promise.resolve().then(() => {
console.log('4');
});
process.nextTick(() => {
console.log('5');
});
console.log('6');
输出:1 → 6 → 5 → 4 → 2 → 3
解析:
1、6—— 同步代码,立即执行5——process.nextTick微任务,最高优先级4——Promise.then微任务,次高优先级2——setTimeout宏任务(timers 阶段)3——setImmediate宏任务(check 阶段)
注意:
setTimeout(fn, 0)和setImmediate在主模块中的顺序不确定,但在 I/O 回调中setImmediate总是先执行。
四、高频面试题
Q1:Node.js 的单线程是什么意思?
JS 执行是单线程的,但 Node.js 底层并不是单线程。libuv 维护了一个线程池(默认 4 个线程)处理文件 I/O 等操作。网络 I/O 使用的是操作系统的异步机制(epoll/kqueue)。
Q2:Node.js 适用于什么场景?
- RESTful API 服务
- 实时应用(聊天室、协作工具)
- 微服务网关
- 服务端渲染(SSR)
- CLI 工具
- 流式数据处理
Q3:如何解决单线程 CPU 密集问题?
worker_threads—— Node.js 内置的工作线程child_process—— 开子进程cluster模块 —— 多进程利用多核 CPU- 把计算密集任务交给 C++ 插件(N-API)