Node.js 面试题详细答案 - Q3
Q3: 什么是调用栈(Call Stack)、任务队列(Task Queue)和微任务队列(Microtask Queue)?process.nextTick 和 Promise 哪个优先级更高?
调用栈(Call Stack)
调用栈是 JavaScript 引擎用来追踪函数调用的数据结构,遵循 LIFO(后进先出)原则。
function first() {
console.log('first 开始')
second()
console.log('first 结束')
}
function second() {
console.log('second 开始')
third()
console.log('second 结束')
}
function third() {
console.log('third 执行')
}
first()
// 调用栈执行过程:
// 1. first() 入栈
// 2. second() 入栈
// 3. third() 入栈
// 4. third() 出栈
// 5. second() 出栈
// 6. first() 出栈
任务队列(Task Queue / Macro Task Queue)
任务队列存储宏任务,包括 setTimeout、setInterval、setImmediate、I/O 操作等。
console.log('1. 同步代码')
setTimeout(() => {
console.log('2. setTimeout 回调')
}, 0)
setTimeout(() => {
console.log('3. 另一个 setTimeout 回调')
}, 0)
console.log('4. 同步代码结束')
// 输出:
// 1. 同步代码
// 4. 同步代码结束
// 2. setTimeout 回调
// 3. 另一个 setTimeout 回调
微任务队列(Microtask Queue)
微任务队列存储微任务,包括 Promise、process.nextTick、queueMicrotask 等。
console.log('1. 同步代码')
Promise.resolve().then(() => {
console.log('2. Promise 回调')
})
queueMicrotask(() => {
console.log('3. queueMicrotask 回调')
})
console.log('4. 同步代码结束')
// 输出:
// 1. 同步代码
// 4. 同步代码结束
// 2. Promise 回调
// 3. queueMicrotask 回调
优先级比较
process.nextTick 优先级最高,其次是 Promise,最后是宏任务。
console.log('1. 同步代码开始')
setTimeout(() => {
console.log('2. setTimeout')
}, 0)
setImmediate(() => {
console.log('3. setImmediate')
})
Promise.resolve().then(() => {
console.log('4. Promise')
})
process.nextTick(() => {
console.log('5. process.nextTick')
})
queueMicrotask(() => {
console.log('6. queueMicrotask')
})
console.log('7. 同步代码结束')
// 输出顺序:
// 1. 同步代码开始
// 7. 同步代码结束
// 5. process.nextTick (最高优先级)
// 4. Promise
// 6. queueMicrotask
// 2. setTimeout
// 3. setImmediate
详细优先级示例
// 复杂优先级示例
console.log('=== 开始 ===')
// 宏任务
setTimeout(() => console.log('setTimeout 1'), 0)
setImmediate(() => console.log('setImmediate 1'))
// 微任务
Promise.resolve().then(() => {
console.log('Promise 1')
// 在微任务中创建新的微任务
process.nextTick(() => console.log('nextTick in Promise'))
Promise.resolve().then(() => console.log('Promise in Promise'))
})
process.nextTick(() => {
console.log('nextTick 1')
// 在 nextTick 中创建新的微任务
process.nextTick(() => console.log('nextTick in nextTick'))
Promise.resolve().then(() => console.log('Promise in nextTick'))
})
console.log('=== 结束 ===')
// 输出:
// === 开始 ===
// === 结束 ===
// nextTick 1
// nextTick in nextTick
// Promise in nextTick
// Promise 1
// nextTick in Promise
// Promise in Promise
// setTimeout 1
// setImmediate 1
事件循环中的执行顺序
// 事件循环执行顺序
function demonstrateEventLoop() {
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 in nextTick')
})
})
Promise.resolve().then(() => {
console.log('6. Promise')
// 嵌套微任务
process.nextTick(() => {
console.log('7. nextTick in Promise')
})
})
console.log('8. 同步代码结束')
}
demonstrateEventLoop()
// 输出:
// 1. 同步代码
// 8. 同步代码结束
// 4. process.nextTick
// 7. nextTick in Promise
// 6. Promise
// 5. Promise in nextTick
// 2. setTimeout
// 3. setImmediate
实际应用场景
// 利用优先级优化性能
function processData(data) {
console.log('开始处理数据')
// 使用 process.nextTick 确保在下一个事件循环前执行
process.nextTick(() => {
console.log('数据预处理完成')
// 使用 Promise 处理异步操作
return Promise.resolve()
.then(() => {
console.log('异步处理开始')
// 模拟异步操作
return new Promise((resolve) => setTimeout(resolve, 100))
})
.then(() => {
console.log('异步处理完成')
})
})
}
processData('test data')
总结
- 调用栈:存储函数调用,LIFO 结构
- 任务队列:存储宏任务(setTimeout、setImmediate 等)
- 微任务队列:存储微任务(Promise、process.nextTick 等)
- 优先级:process.nextTick > Promise > 宏任务
- 理解这些概念对于编写高效的异步代码至关重要