JavaScript事件循环面试题:setTimeout和Promise的执行顺序
在JavaScript面试中,关于setTimeout和Promise的执行顺序问题是高频考点。要准确解答这类问题,无需死记硬背,只需掌握一套标准化的分析流程。以下是解决该问题的核心步骤和逻辑解析。
1. 理解核心机制:事件循环
JavaScript是单线程语言,但它通过事件循环机制实现异步操作。要搞懂执行顺序,必须区分三种任务类型:
- 同步任务:在主线程上直接执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
- 微任务:优先级高于宏任务,在一个宏任务执行完后立即执行。
- 宏任务:包括
setTimeout、setInterval、I/O操作等,主线程每次只从宏任务队列中取一个执行。
2. 掌握“万能”执行步骤
无论代码多么复杂,严格按照以下四个步骤进行分析,即可得出正确答案:
- 执行所有同步代码。
- 检查微任务队列,如有微任务,执行队列中的所有微任务,直到清空。
- 执行宏任务队列中的一个任务。
- 重复步骤2和步骤3,直到所有任务队列清空。
3. 实战演练:经典面试题
请看下面这段代码,尝试判断输出顺序。
console.log('1');
setTimeout(function() {
console.log('2');
Promise.resolve().then(function() {
console.log('3');
});
}, 0);
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5');
});
console.log('6');
第一轮循环分析
执行同步代码:
- 遇到
console.log('1'),输出1。 - 遇到
setTimeout,将其回调函数放入宏任务队列(记为宏任务1)。 - 遇到
new Promise,Promise构造函数是同步执行的,输出4。resolve()被调用,将.then回调放入微任务队列(记为微任务1)。 - 遇到
console.log('6'),输出6。
此时同步代码执行完毕,当前输出为:1, 4, 6。
检查微任务队列:
- 发现微任务队列中有
微任务1(即console.log('5'))。 - 执行微任务1,输出
5。
此时微任务队列已清空。第一轮循环结束,当前输出为:1, 4, 6, 5。
第二轮循环分析
执行宏任务队列中的一个任务:
- 取出
宏任务1(setTimeout的回调)并执行。 - 遇到
console.log('2'),输出2。 - 遇到
Promise.resolve().then(...),将其回调放入微任务队列(记为微任务2)。
检查微任务队列:
- 发现微任务队列中有
微任务2(即console.log('3'))。 - 执行微任务2,输出
3。
此时微任务队列再次清空。第二轮循环结束。
4. 验证与总结
根据上述步骤推导,最终的执行顺序为:
1
4
6
5
2
3
为了方便记忆和快速识别,参考下表对比常见任务类型:
| 任务类型 | 常见API | 执行优先级 |
|---|---|---|
| 同步任务 | console.log, 主线程代码 |
最高 (立即执行) |
| 微任务 | Promise.then, process.nextTick (Node.js) |
高 (每次宏任务后执行) |
| 宏任务 | setTimeout, setInterval, I/O |
低 (每次循环取一个) |
5. 事件循环流程图
为了更直观地理解代码在引擎中的流转过程,请参考以下逻辑流图:
graph TD
A["Start"] --> B["Execute Sync Code"]
B --> C{Microtask Queue Empty?}
C -- No --> D["Execute All Microtasks"]
D --> C
C -- Yes --> E{Macrotask Queue Empty?}
E -- No --> F["Execute One Macrotask"]
F --> C
E -- Yes --> G["End"]
记住:宏任务是一个接一个地吃,微任务是一口全吃完。只要看到Promise.then,就知道它必须抢在下一个setTimeout之前执行。

暂无评论,快来抢沙发吧!