Node.js 面试题详细答案 - Q2
Q2: 解释 Node.js 的事件循环(Event Loop)机制。它由哪些阶段组成?
事件循环概述
事件循环是 Node.js 的核心机制,负责执行代码、收集和处理事件,以及执行队列中的子任务。
事件循环的六个阶段
// 事件循环的六个阶段
// 1. timers - 执行 setTimeout 和 setInterval 回调
// 2. pending callbacks - 执行 I/O 回调
// 3. idle, prepare - 内部使用
// 4. poll - 获取新的 I/O 事件
// 5. check - 执行 setImmediate 回调
// 6. close callbacks - 执行 close 事件回调
详细阶段说明
1. Timers 阶段
执行 setTimeout 和 setInterval 的回调函数。
console.log('开始')
setTimeout(() => {
console.log('Timer 1')
}, 0)
setTimeout(() => {
console.log('Timer 2')
}, 0)
console.log('结束')
// 输出:
// 开始
// 结束
// Timer 1
// Timer 2
2. Pending Callbacks 阶段
执行延迟到下一个循环迭代的 I/O 回调。
const fs = require('fs')
fs.readFile('file.txt', (err, data) => {
console.log('文件读取完成')
})
3. Poll 阶段
获取新的 I/O 事件,执行相关的回调函数。
// Poll 阶段会检查是否有新的 I/O 事件
// 如果有,立即执行回调
// 如果没有,会等待新的 I/O 事件
4. Check 阶段
执行 setImmediate 的回调函数。
console.log('开始')
setImmediate(() => {
console.log('setImmediate 回调')
})
console.log('结束')
// 输出:
// 开始
// 结束
// setImmediate 回调
5. Close Callbacks 阶段
执行 close 事件的回调函数。
const server = require('http').createServer()
server.on('close', () => {
console.log('服务器关闭')
})
server.close()
微任务队列
在事件循环的每个阶段之间,会处理微任务队列:
console.log('开始')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log('Promise')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('结束')
// 输出:
// 开始
// 结束
// Promise (微任务优先)
// setTimeout
// setImmediate
完整的事件循环示例
const fs = require('fs')
console.log('1. 同步代码开始')
setTimeout(() => {
console.log('2. setTimeout 回调')
}, 0)
setImmediate(() => {
console.log('3. setImmediate 回调')
})
process.nextTick(() => {
console.log('4. process.nextTick 回调')
})
Promise.resolve().then(() => {
console.log('5. Promise 回调')
})
fs.readFile(__filename, () => {
console.log('6. 文件读取回调')
setTimeout(() => {
console.log('7. 文件读取中的 setTimeout')
}, 0)
setImmediate(() => {
console.log('8. 文件读取中的 setImmediate')
})
})
console.log('9. 同步代码结束')
// 输出顺序:
// 1. 同步代码开始
// 9. 同步代码结束
// 4. process.nextTick 回调
// 5. Promise 回调
// 2. setTimeout 回调
// 3. setImmediate 回调
// 6. 文件读取回调
// 8. 文件读取中的 setImmediate
// 7. 文件读取中的 setTimeout
优先级总结
- 同步代码 - 最高优先级
- process.nextTick - 微任务,优先级最高
- Promise - 微任务
- setImmediate - 宏任务
- setTimeout/setInterval - 宏任务
实际应用
// 利用事件循环优化性能
function heavyTask() {
return new Promise((resolve) => {
// 将重任务分解为小块
function processChunk() {
// 处理一小块数据
if (/* 还有数据 */) {
setImmediate(processChunk); // 让出控制权
} else {
resolve();
}
}
processChunk();
});
}
总结
事件循环是 Node.js 实现非阻塞 I/O 的核心机制,通过六个阶段的循环执行,配合微任务队列,实现了高效的异步编程模型。理解事件循环对于编写高性能的 Node.js 应用至关重要。