为什么理解微任务是理解 JavaScript 并发的关键
JavaScript 是单线程的,通过事件循环(Event Loop)处理异步操作。但事件循环中有两种不同的队列:宏任务队列(Task Queue)和微任务队列(Microtask Queue)。
理解它们的区别,是理解 Promise、async/await、MutationObserver 等异步机制的基础。
一、任务队列 vs 微任务队列
1.1 基本区别
| 特性 | 宏任务(Task/Macrotask) | 微任务(Microtask) |
|---|---|---|
| 执行时机 | 当前任务完成后,下一个任务开始前 | 当前任务完成后,立即执行 |
| 队列数 | 多个 | 只有一个 |
| 包含 | setTimeout、setInterval、I/O、UI 渲染 | Promise、queueMicrotask、MutationObserver |
| 每次迭代 | 执行一个宏任务 | 执行所有微任务 |
1.2 执行顺序示意
当前执行栈为空
↓
执行所有微任务(清空微任务队列)
↓
执行一个宏任务
↓
执行所有微任务(清空微任务队列)
↓
执行下一个宏任务
↓
...(重复)
二、微任务执行时机
2.1 Promise 的微任务
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序:
// 1
// 4
// 3
// 2
分析:
- 同步代码先执行:
1,4 - 微任务(Promise)加入微任务队列
- 宏任务(setTimeout)加入宏任务队列
- 同步代码执行完毕,清空微任务队列:
3 - 事件循环下一个迭代,取出并执行宏任务:
2
2.2 嵌套的 Promise
console.log('start');
Promise.resolve()
.then(() => console.log('promise1'))
.then(() => console.log('promise2'));
Promise.resolve()
.then(() => console.log('promise3'));
console.log('end');
// 输出:
// start
// end
// promise1
// promise3
// promise2
2.3 微任务中抛出错误
Promise.resolve()
.then(() => {
throw new Error('error in microtask');
})
.catch(err => console.log('caught:', err.message));
// 错误在下一个微任务中被处理
// 输出:caught: error in microtask
三、queueMicrotask
3.1 基本用法
queueMicrotask 是标准的将回调加入微任务队列的方法:
queueMicrotask(() => {
console.log('microtask');
});
3.2 与 Promise.resolve().then 的区别
两者基本等价:
// 这两种方式效果相同
queueMicrotask(() => console.log('microtask'));
Promise.resolve().then(() => console.log('microtask'));
queueMicrotask 的优势是语义更明确,且不需要创建 Promise。
3.3 实际应用:确保异步执行
let state = 'initial';
const obj = {};
// 同步代码无法观测到的修改
queueMicrotask(() => {
state = 'updated';
console.log('State updated to:', state);
});
console.log('Current state:', state);
// 输出:
// Current state: initial
// State updated to: updated
四、MutationObserver
4.1 MutationObserver 是微任务
MutationObserver 的回调在微任务队列中执行:
const observer = new MutationObserver(() => {
console.log('mutation');
});
const el = document.createElement('div');
observer.observe(el, { attributes: true });
el.setAttribute('data-test', 'value');
console.log('after attribute change');
// 输出:
// after attribute change
// mutation
4.2 批量 DOM 变更
MutationObserver 会将多次 DOM 变更合并为一次回调:
const observer = new MutationObserver(() => {
console.log('mutations:', mutations.length);
});
const el = document.createElement('div');
observer.observe(el, { attributes: true });
el.setAttribute('a', '1');
el.setAttribute('b', '2');
el.setAttribute('c', '3');
console.log('after all changes');
// 输出:
// after all changes
// mutations: 3(合并为一次)
五、async/await 与微任务
5.1 async 函数返回 Promise
async function foo() {
console.log('2');
return 'result';
}
console.log('1');
foo().then(console.log);
console.log('3');
// 输出:
// 1
// 2
// 3
// result
5.2 await 后的代码在微任务中执行
async function foo() {
console.log('2');
await Promise.resolve();
console.log('4');
}
console.log('1');
foo();
console.log('3');
// 输出:
// 1
// 2
// 3
// 4
await 之后的代码相当于在 .then() 中执行,即微任务。
六、面试高频考点
考点 1:宏任务与微任务的区别
微任务在当前任务结束后、下一个宏任务开始前立即执行,且会清空整个队列。宏任务每次只执行一个。
考点 2:输出顺序问题
给定一段包含 setTimeout、Promise、async/await 的代码,能准确分析输出顺序。
考点 3:Promise.then vs setTimeout
Promise.then 是微任务,setTimeout 是宏任务。微任务优先于宏任务执行。