JavaScript 是一种单线程语言,所有代码都在主线程上运行。为了支持异步操作,JavaScript 使用了 事件循环(Event Loop) ,通过调度 微任务(Microtasks) 和 宏任务(Macrotasks) 实现非阻塞的异步执行。
在本篇文章中,我们将通过解析多个经典例题,逐步深入理解事件循环的工作原理和微任务、宏任务的交互机制。
什么是微任务和宏任务?
- 微任务(Microtasks) :
微任务在当前事件循环(Event Loop)阶段内执行,优先于下一轮宏任务。
常见例子:Promise.then()、MutationObserver、queueMicrotask()。 - 宏任务(Macrotasks) :
宏任务在事件循环的下一阶段执行。每轮事件循环完成后,宏任务才开始执行。
常见例子:setTimeout、setInterval、setImmediate(Node.js)。
事件循环的规则
- 执行同步代码(调用栈为空时停止)。
- 清空微任务队列(按先进先出的顺序)。
- 取出第一个宏任务执行,并重复步骤 1 和 2。
经典例题解析
以下通过多个经典例题展示微任务与宏任务的交替执行顺序。
例题 1: 多条 Promise 链
代码
Promise.resolve().then(() => {
console.log(0);
return new Promise((resolve) => { resolve(4); });
}).then((res) => {
console.log(res);
});
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
});
输出
0
1
2
3
4
5
6
解析
- 第一条链:
Promise.resolve()后,console.log(0)立即执行,随后返回新的 Promise,这使得console.log(4)的执行被延迟到下一轮微任务队列。 - 第二条链:所有
.then()按顺序注册微任务,输出1 -> 2 -> 3 -> 5 -> 6。 - 由于第二条链的
.then()注册早于console.log(4)的微任务,因此4在3之后输出。
例题 2: 宏任务与微任务的交替
代码
console.log('start');
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(() => {
console.log('promise1');
});
}, 0);
setTimeout(() => {
console.log('timeout2');
}, 0);
Promise.resolve().then(() => {
console.log('promise2');
}).then(() => {
console.log('promise3');
});
console.log('end');
输出
start
end
promise2
promise3
timeout1
promise1
timeout2
解析
-
同步代码优先执行:
start和end输出。 -
微任务队列清空:
promise2和promise3。 -
宏任务按顺序执行:
- 第一个
setTimeout输出timeout1,随后执行其内的微任务promise1。 - 第二个
setTimeout输出timeout2。
- 第一个
例题 3: 微任务中嵌套宏任务
代码
console.log('start');
Promise.resolve().then(() => {
console.log('promise1');
setTimeout(() => {
console.log('timeout1');
}, 0);
}).then(() => {
console.log('promise2');
});
setTimeout(() => {
console.log('timeout2');
}, 0);
console.log('end');
输出
start
end
promise1
promise2
timeout2
timeout1
解析
-
同步代码先执行:
start和end。 -
微任务队列:
promise1输出,注册timeout1。promise2输出。
-
宏任务队列:
timeout2输出。timeout1在下一轮宏任务中执行。
例题 4: 动态注册微任务
代码
Promise.resolve().then(() => {
console.log('A');
return Promise.resolve('B').then(res => {
console.log(res);
});
}).then(() => {
console.log('C');
});
输出
A
B
C
解析
Promise.resolve().then()执行,输出A。- 嵌套的
Promise.resolve('B')注册新的微任务,输出B。 - 外层链的下一个
.then()被延迟到嵌套任务完成后执行,输出C。
例题 5: 微任务和宏任务的复杂交互
代码
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(() => {
console.log('promise1');
}).then(() => {
console.log('promise2');
});
}, 0);
setTimeout(() => {
console.log('timeout2');
}, 0);
Promise.resolve().then(() => {
console.log('promise3');
}).then(() => {
console.log('promise4');
});
输出
promise3
promise4
timeout1
promise1
promise2
timeout2
解析
-
微任务队列:
promise3输出。promise4紧随其后。
-
宏任务队列:
timeout1输出,执行其内的promise1和promise2微任务。timeout2最后输出。
学习要点
- 微任务优先级高于宏任务。
- 微任务队列遵循先进先出(FIFO)原则。
- 宏任务之间的间隔会清空微任务队列。