事件循環(huán)(Event Loop)練習(xí)題,幫助你鞏固對(duì)宏任務(wù)、微任務(wù)和異步執(zhí)行順序的理解。每個(gè)題目都附有詳細(xì)解析:
題目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 - 微任務(wù)隊(duì)列:
- 第一個(gè) Promise:輸出
Promise 1,添加新的宏任務(wù)(Timeout 2) - 第二個(gè) Promise:輸出
Promise 2
- 第一個(gè) Promise:輸出
- 宏任務(wù)隊(duì)列:
- 執(zhí)行第一個(gè) setTimeout:輸出
Timeout 1 - 執(zhí)行第二個(gè) setTimeout:輸出
Timeout 2
</details>
- 執(zhí)行第一個(gè) 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 - 微任務(wù)隊(duì)列:
- 第一個(gè) then:輸出
promise1,返回新 Promise - 第二個(gè) then:輸出
promise2,添加新宏任務(wù)
- 第一個(gè) then:輸出
- 宏任務(wù)隊(duì)列:
- 第一個(gè) setTimeout:輸出
setTimeout - 第二個(gè) setTimeout:輸出
inner setTimeout
</details>
- 第一個(gè) 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 - 微任務(wù)隊(duì)列:
- async2 的 await:輸出
E - async1 的 await:輸出
B - Promise 的 then:輸出
I
- async2 的 await:輸出
- 宏任務(wù):
G
</details>
題目4:復(fù)雜微任務(wù)鏈
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 - 微任務(wù)隊(duì)列:
- 第一個(gè) Promise 鏈:
3→4→5 - 第二個(gè) Promise 鏈:
6→7 - 注意:
return Promise.resolve會(huì)創(chuàng)建額外的微任務(wù)
- 第一個(gè) Promise 鏈:
- 宏任務(wù):
2
</details>
題目5:事件循環(huán)綜合題
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>
初始輸出順序(不觸發(fā)點(diǎn)擊):
Start
End
Promise 1
Timeout
Microtask in Timeout
如果觸發(fā)點(diǎn)擊事件:
Start
End
Promise 1
Timeout
Microtask in Timeout
Click
Microtask in Click
解析:
- 同步代碼:
Start→End - 微任務(wù):
Promise 1 - 宏任務(wù):
Timeout及其微任務(wù)Microtask in Timeout - UI事件(點(diǎn)擊)作為宏任務(wù)處理
- 每個(gè)宏任務(wù)后都會(huì)清空微任務(wù)隊(duì)列
</details>
解題技巧總結(jié):
- 同步代碼總是最先執(zhí)行
-
微任務(wù)(Microtask)執(zhí)行時(shí)機(jī):
- 在每個(gè)宏任務(wù)之后
- 在DOM渲染之前
- 清空整個(gè)微任務(wù)隊(duì)列(包括嵌套產(chǎn)生的)
-
宏任務(wù)(Macrotask)執(zhí)行時(shí)機(jī):
- 一次事件循環(huán)只執(zhí)行一個(gè)宏任務(wù)
- 包括:setTimeout、setInterval、I/O、UI渲染、事件回調(diào)
-
async/await 本質(zhì):
-
await之前的代碼是同步的 -
await之后的代碼相當(dāng)于.then()回調(diào)
-
-
Promise 鏈:
- 每個(gè)
.then()都會(huì)創(chuàng)建新的微任務(wù) -
return Promise會(huì)創(chuàng)建額外的微任務(wù)
- 每個(gè)
建議你嘗試自己分析這些題目,畫(huà)出事件循環(huán)的流程圖,然后對(duì)照解析驗(yàn)證理解。掌握這些模式后,你就能準(zhǔn)確預(yù)測(cè)任何JavaScript異步代碼的執(zhí)行順序了!