事件循环(Event Loop)练习题,帮助你巩固对宏任务、微任务和异步执行顺序的理解。每个题目都附有详细解析:
题目1:混合 Promise 和 setTimeout
console.log('Start');
setTimeout(() => console.log('Timeout 1'), 0);
Promise.resolve().then(() => {
console.log('Promise 1');
setTimeout(() => console.log('Timeout 2'), 0);
});
Promise.resolve().then(() => console.log('Promise 2'));
console.log('End');
<details>
<summary>查看答案与解析</summary>
输出顺序:
Start
End
Promise 1
Promise 2
Timeout 1
Timeout 2
解析:
- 同步代码:
Start
→End
- 微任务队列:
- 第一个 Promise:输出
Promise 1
,添加新的宏任务(Timeout 2) - 第二个 Promise:输出
Promise 2
- 第一个 Promise:输出
- 宏任务队列:
- 执行第一个 setTimeout:输出
Timeout 1
- 执行第二个 setTimeout:输出
Timeout 2
</details>
- 执行第一个 setTimeout:输出
题目2:多层异步嵌套
console.log('Script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
setTimeout(() => console.log('inner setTimeout'), 0);
});
console.log('Script end');
<details>
<summary>查看答案与解析</summary>
输出顺序:
Script start
Script end
promise1
promise2
setTimeout
inner setTimeout
解析:
- 同步代码:
Script start
→Script end
- 微任务队列:
- 第一个 then:输出
promise1
,返回新 Promise - 第二个 then:输出
promise2
,添加新宏任务
- 第一个 then:输出
- 宏任务队列:
- 第一个 setTimeout:输出
setTimeout
- 第二个 setTimeout:输出
inner setTimeout
</details>
- 第一个 setTimeout:输出
题目3:async/await 与 Promise 混合
async function async1() {
console.log('A');
await async2();
console.log('B');
}
async function async2() {
console.log('C');
await new Promise(resolve => {
console.log('D');
resolve();
});
console.log('E');
}
console.log('F');
setTimeout(() => console.log('G'), 0);
async1();
new Promise(resolve => {
console.log('H');
resolve();
}).then(() => console.log('I'));
console.log('J');
<details>
<summary>查看答案与解析</summary>
输出顺序:
F
A
C
D
H
J
E
B
I
G
解析:
- 同步代码:
F
→A
→C
→D
→H
→J
- 微任务队列:
- async2 的 await:输出
E
- async1 的 await:输出
B
- Promise 的 then:输出
I
- async2 的 await:输出
- 宏任务:
G
</details>
题目4:复杂微任务链
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve()
.then(() => {
console.log('3');
return Promise.resolve('4').then(data => {
console.log(data);
return '5';
});
})
.then(data => console.log(data));
Promise.resolve()
.then(() => console.log('6'))
.then(() => console.log('7'));
console.log('8');
<details>
<summary>查看答案与解析</summary>
输出顺序:
1
8
3
6
4
7
5
2
解析:
- 同步代码:
1
→8
- 微任务队列:
- 第一个 Promise 链:
3
→4
→5
- 第二个 Promise 链:
6
→7
- 注意:
return Promise.resolve
会创建额外的微任务
- 第一个 Promise 链:
- 宏任务:
2
</details>
题目5:事件循环综合题
console.log('Start');
document.addEventListener('click', () => {
console.log('Click');
Promise.resolve().then(() => console.log('Microtask in Click'));
});
setTimeout(() => {
console.log('Timeout');
Promise.resolve().then(() => console.log('Microtask in Timeout'));
}, 0);
Promise.resolve().then(() => console.log('Promise 1'));
console.log('End');
<details>
<summary>查看答案与解析</summary>
初始输出顺序(不触发点击):
Start
End
Promise 1
Timeout
Microtask in Timeout
如果触发点击事件:
Start
End
Promise 1
Timeout
Microtask in Timeout
Click
Microtask in Click
解析:
- 同步代码:
Start
→End
- 微任务:
Promise 1
- 宏任务:
Timeout
及其微任务Microtask in Timeout
- UI事件(点击)作为宏任务处理
- 每个宏任务后都会清空微任务队列
</details>
解题技巧总结:
- 同步代码总是最先执行
-
微任务(Microtask)执行时机:
- 在每个宏任务之后
- 在DOM渲染之前
- 清空整个微任务队列(包括嵌套产生的)
-
宏任务(Macrotask)执行时机:
- 一次事件循环只执行一个宏任务
- 包括:setTimeout、setInterval、I/O、UI渲染、事件回调
-
async/await 本质:
-
await
之前的代码是同步的 -
await
之后的代码相当于.then()
回调
-
-
Promise 链:
- 每个
.then()
都会创建新的微任务 -
return Promise
会创建额外的微任务
- 每个
建议你尝试自己分析这些题目,画出事件循环的流程图,然后对照解析验证理解。掌握这些模式后,你就能准确预测任何JavaScript异步代码的执行顺序了!