代码示例
javascript
console.log(1);
Promise.resolve().then(res => console.log(2));
setTimeout(() => console.log(3), 0);
执行顺序
输出结果:1 → 2 → 3
原理分析
1. 同步任务优先执行
-
console.log(1)
是同步任务,直接进入主线程执行,输出1
。 -
Promise.resolve()
的构造函数是同步执行的,但.then()
的回调会被注册为微任务(Microtask)。
2. 微任务队列(Microtask Queue)
-
.then(res => console.log(2))
的回调被放入微任务队列。 - 微任务优先级高于宏任务:在当前同步任务执行完毕后,事件循环会优先清空微任务队列。
3. 宏任务队列(Macrotask Queue)
-
setTimeout
的回调被放入宏任务队列。 - 宏任务需等待当前所有微任务执行完毕后才会执行。
4. 事件循环流程
-
执行同步任务 :输出
1
。 -
注册微任务 :将
.then
回调加入微任务队列。 -
注册宏任务 :将
setTimeout
回调加入宏任务队列。 -
同步任务结束,开始处理微任务队列:
- 执行
.then
回调,输出2
。
- 执行
-
微任务队列清空,开始处理宏任务队列:
- 执行
setTimeout
回调,输出3
。
- 执行
关键概念
1. 任务队列分类
任务类型 | 示例 | 优先级 |
---|---|---|
同步任务 | console.log |
最高 |
微任务 | Promise.then |
次高(当前轮次) |
宏任务 | setTimeout /setInterval |
最低(下一轮次) |
2. 事件循环核心规则
- 同步任务:立即执行,阻塞后续代码。
- 微任务 :在 当前宏任务结束后、下一个宏任务开始前 执行。
- 宏任务:每次事件循环仅执行一个,由任务队列顺序决定。
浏览器与 Node.js 的差异
- 浏览器环境:微任务在 DOM 渲染前执行。
- Node.js :微任务在
process.nextTick
之后、setImmediate
之前执行。
复杂场景扩展
若代码改为:
javascript
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => console.log(3));
}, 0);
Promise.resolve().then(() => console.log(4));
输出顺序:1 → 4 → 2 → 3
原因:
- 同步任务输出
1
。 - 注册
setTimeout
(宏任务)和Promise.then
(微任务)。 - 同步任务结束后,先执行微任务输出
4
。 - 执行宏任务
setTimeout
,输出2
,其回调中的Promise.then
又注册一个微任务,输出3
。
总结
- 同步任务:直接执行,无延迟。
- 微任务:在当前事件循环末尾执行(如 Promise 回调)。
- 宏任务:在下一次事件循环开始时执行(如 setTimeout)。
理解这一机制,能精准掌握异步代码的执行顺序,避免常见的回调地狱问题。