网站
引入
在 JS 执行机制 里学过,异步回调会放到任务队列里排队。
但任务队列其实不止一个。
JS 里有宏任务和微任务两种队列,它们的执行优先级不同,决定了异步代码的执行顺序。
正文
定义
定义
事件循环中,宏任务(Macro Task)和微任务(Micro Task)是两类不同的异步任务。每次事件循环会先执行一个宏任务,然后把所有微任务清空,再进入下一轮。
宏任务
宏任务是常见的异步操作,包括:
setTimeoutsetInterval- I/O 操作(网络请求、文件读写)
- UI 渲染(页面更新)
setImmediate(Node.js)
setTimeout(function () {
console.log("宏任务:setTimeout");
}, 0);
setInterval(function () {
console.log("宏任务:setInterval");
}, 1000);每个宏任务执行完后,浏览器可能会进行 UI 渲染。
微任务
微任务的优先级比宏任务高,包括:
Promise.then、Promise.catch、Promise.finallyMutationObserver(DOM 变化监听)queueMicrotaskprocess.nextTick(Node.js)
Promise.resolve().then(function () {
console.log("微任务:Promise.then");
});执行顺序
一轮事件循环的执行顺序:
- 执行当前同步代码(宏任务的一部分)
- 同步代码执行完后,清空所有微任务
- 微任务清空后,可能进行 UI 渲染
- 取下一个宏任务执行
- 回到第 2 步
用一个表来总结:
| 类型 | 常见任务 | 优先级 |
|---|---|---|
| 同步代码 | 普通语句 | 最先执行 |
| 微任务 | Promise.then、MutationObserver | 同步之后、宏任务之前 |
| 宏任务 | setTimeout、setInterval、UI 渲染 | 微任务之后 |
基础例子
console.log("1 - 同步");
setTimeout(function () {
console.log("2 - 宏任务");
}, 0);
Promise.resolve().then(function () {
console.log("3 - 微任务");
});
console.log("4 - 同步");执行过程:
console.log("1 - 同步")— 同步代码,直接执行setTimeout— 回调放入宏任务队列Promise.resolve().then()— 回调放入微任务队列console.log("4 - 同步")— 同步代码,直接执行- 同步代码完成,清空微任务队列,执行
Promise.then回调 - 微任务清空,取宏任务队列中的
setTimeout回调执行
输出:
1 - 同步
4 - 同步
3 - 微任务
2 - 宏任务微任务可以插入更多微任务
微任务执行过程中产生的新微任务,会在本轮就执行完。
Promise.resolve()
.then(function () {
console.log("微任务 1");
return Promise.resolve();
})
.then(function () {
console.log("微任务 2");
});
setTimeout(function () {
console.log("宏任务");
}, 0);输出:
微任务 1
微任务 2
宏任务微任务链式调用产生的新微任务,全部在当前轮清空,不会等到下一轮。
宏任务之间是独立的
每个宏任务执行完后,才会去清空微任务队列。
setTimeout(function () {
console.log("宏任务 1");
Promise.resolve().then(function () {
console.log("宏任务 1 的微任务");
});
}, 0);
setTimeout(function () {
console.log("宏任务 2");
}, 0);输出:
宏任务 1
宏任务 1 的微任务
宏任务 2第一个宏任务执行时产生的微任务,会在第二个宏任务开始前清空。
综合题目
这道题包含了同步、微任务、宏任务的混合:
console.log("开始");
setTimeout(function () {
console.log("setTimeout 1");
}, 0);
Promise.resolve()
.then(function () {
console.log("Promise 1");
})
.then(function () {
console.log("Promise 2");
});
setTimeout(function () {
console.log("setTimeout 2");
}, 0);
console.log("结束");逐步分析:
console.log("开始")— 同步,直接输出setTimeout回调 1 — 放入宏任务队列Promise.then第一个 — 放入微任务队列setTimeout回调 2 — 放入宏任务队列console.log("结束")— 同步,直接输出- 同步完成,清空微任务:输出
Promise 1,再输出Promise 2 - 取宏任务 1:输出
setTimeout 1 - 取宏任务 2:输出
setTimeout 2
输出:
开始
结束
Promise 1
Promise 2
setTimeout 1
setTimeout 2带 async/await 的题目
async 函数中 await 后面的代码相当于放到微任务里。
async function test() {
console.log("async 1");
await Promise.resolve();
console.log("async 2");
}
console.log("同步 1");
test();
console.log("同步 2");执行过程:
console.log("同步 1")— 同步输出- 调用
test(),进入函数体 console.log("async 1")— 同步输出await— 后面的代码相当于微任务- 回到外部,
console.log("同步 2")— 同步输出 - 同步完成,执行微任务:输出
async 2
输出:
同步 1
async 1
同步 2
async 2面试常见套路
| 套路 | 说明 |
|---|---|
setTimeout(fn, 0) 和 Promise.then 比较 | 微任务先于宏任务 |
async 函数中 await 前后的代码 | await 前是同步,后面是微任务 |
多个 setTimeout 和多个 Promise | 微任务全部清完才轮到下一个宏任务 |
| 宏任务中产生微任务 | 当前宏任务结束后、下一个宏任务开始前清空 |
| 微任务中产生微任务 | 本轮就执行完,不会拖到下一轮 |
特点
- 同步代码最先执行
- 微任务优先级高于宏任务
- 每轮事件循环:一个宏任务 → 所有微任务 → 可能 UI 渲染 → 下一个宏任务
- 微任务中新增的微任务在本轮就执行
setTimeout(fn, 0)不等于立即执行,要等当前同步和微任务都完成
理解
把事件循环理解成餐厅的点单系统。
同步代码是厨师手上正在做的菜,必须先做完。
微任务是 VIP 订单,厨师做完手上的菜,优先处理所有 VIP 订单,包括 VIP 订单追加的甜品。
宏任务是普通订单,VIP 订单全部处理完,才按顺序做普通订单。
每做完一个普通订单,就检查一下有没有新的 VIP 订单进来。
同步 > 微任务 > 宏任务,这就是 JS 异步代码的执行优先级。
引出
理解了事件循环之后,可以深入学习 JS Promise,了解微任务最常用的载体。
也可以学习 JS 异步编程,了解从回调到 Promise 再到 async/await 的演变过程。
还可以结合 JS 定时器,理解 setTimeout 和 setInterval 在事件循环中的表现。