skFeTeam 本文做者:高揚前端
在前端面試過程當中常常會問到關於代碼執行順序的問題,一般考察的是對瀏覽器的事件循環、宏任務及微任務的理解深度。掌握這些,對於平常寫代碼、面試都有幫助。git
首先,咱們先來看一道題 題1:github
(你們能夠邊看邊在內心想出本身以爲正確的答案,下面每道題都會帶有gif格式圖片的題目解析,幫助你們理解~ ps: 圖中數字表明代碼行數)面試
1 console.log(1);
2 setTimeout(() => {
3 console.log(2);
4 Promise.resolve().then(() => {
5 console.log(3);
6 });
7 }, 0);
8 new Promise((resolve, reject) => {
9 console.log(4);
10 resolve(5);
11 }).then((data) => {
12 console.log(data);
13 });
14 setTimeout(() => {
15 console.log(6);
16 }, 0);
複製代碼
答案是:1 4 5 2 3 6promise
這道題其實考察的是對瀏覽器中事件循環的瞭解程度。衆所周知,瀏覽器中JS代碼單線程執行,全部的同步代碼在主線程中執行,造成執行棧。那瀏覽器中的異步機制是如何實現的呢? 首先,異步任務會被依次放入異步任務隊列中,當主線程中的同步任務完成之後,瀏覽器會輪詢去異步任務隊列中取出異步任務來執行。瀏覽器
JS代碼中的異步任務可進一步分爲宏任務(macrotask)與微任務(microtask)。 宏任務包括:script代碼、setTimeout、setInterval、I/O、UI render 微任務包括:promise.then、Object.observe(已廢棄)、MutationObserverbash
宏任務和微任務會被加入各自的隊列中。 當主線程執行完畢後,瀏覽器會先去清空微任務隊列,依次取出微任務隊列中的微任務執行,執行過程當中若是產生新的微任務,則追加到當前微任務隊列的末尾等待執行。 當微任務隊列清空後,瀏覽器會從宏任務隊列中取出一個宏任務執行。宏任務執行完畢再去清空微任務隊列,微任務隊列清空後再取出一個宏任務來執行。如此反覆,直至宏任務隊列和微任務隊列所有清空。 異步
須要注意的是,宏任務和微任務隊列中新產生的微任務都會追加到 當前微任務隊列隊尾等待執行,而不是追加到下一個循環的微任務隊列中。所以若是微任務隊列的清空過程當中持續產生新的微任務,會形成微任務卡死。 此時再回看題1,可得出以下結論: 輸出與答案一致。題2:async
1 setTimeout(function(){
2 console.log('1')
3 });
4 new Promise(function(resolve){
5 console.log('2');
6 resolve();
7 }).then(function(){
8 console.log('3')
9 });
10 console.log('4');
複製代碼
答案:2 4 3 1ui
在分析當前代碼時須要注意的是,Promise對象的resolve部分的代碼是當前主線程/宏任務的一部分,並不是微任務,Promise對象的then和catch代碼段纔是微任務。所以最早輸出的是2和4,而後纔是微任務隊列中的3,最後是宏任務中的1。
題3:
1 async function async1() {
2 console.log(1);
3 const result = await async2();
4 console.log(3);
5 }
6 async function async2() {
7 console.log(2);
8 }
9 Promise.resolve().then(() => {
10 console.log(4);
11 });
12 setTimeout(() => {
13 console.log(5);
14 });
15 async1();
16 console.log(6);
複製代碼
答案:1 2 6 4 3 5
該題須要注意的是,因爲await方法返回的是一個Promise對象,所以await方法執行完畢後續的代碼都應該納入微任務隊列,所以**console.log(3)**應該被加入微任務隊列等待執行。
題4:
1 function sleep(time) {
2 let startTime = new Date()
3 while (new Date() - startTime < time) {}
4 console.log('1s over')
5 }
複製代碼
1 function sleep(time) {
2 let startTime = new Date()
3 while (new Date() - startTime < time) {}
4 console.log('1s over')
5 }
6 setTimeout(() => {
7 console.log('setTimeout - 1')
8 setTimeout(() => {
9 console.log('setTimeout - 1 - 1')
10 sleep(1000)
11 })
12 new Promise(resolve => {
13 console.log('setTimeout - 1 - resolve')
14 resolve()
15 }).then(() => {
16 console.log('setTimeout - 1 - then')
17 new Promise(resolve => resolve()).then(() => {
18 console.log('setTimeout - 1 - then - then')
19 })
20 })
21 sleep(1000)
22 })
23 setTimeout(() => {
24 console.log('setTimeout - 2')
25 setTimeout(() => {
26 console.log('setTimeout - 2 - 1')
27 sleep(1000)
28 })
29 new Promise(resolve => resolve()).then(() => {
30 console.log('setTimeout - 2 - then')
31 new Promise(resolve => resolve()).then(() => {
32 console.log('setTimeout - 2 - then - then')
33 })
34 })
35 sleep(1000)
36 })
複製代碼
瀏覽器輸出爲:
setTimeout - 1
setTimeout - 1 - resolve
1s over
setTimeout - 1 - then
setTimeout - 1 - then - then
setTimeout - 2
1s over
setTimeout - 2 - then
setTimeout - 2 - then - then
setTimeout - 1 - 1
1s over
setTimeout - 2 - 1
1s over
複製代碼
該題須要注意的是微任務執行過程當中產生的新的微任務也是追加在當前微任務隊列末尾等待執行。
瀏覽器中的事件循環機制較爲簡單,若是將主線程也看做一個宏任務的話,那麼瀏覽器的事件循環機制可看做依次執行如下一、2兩點:
while(true) {
宏任務隊列.shift();
微任務隊列所有任務;
}
複製代碼
須要注意的是:
宏任務隊列和微任務隊列都遵循先進先出原則,先被加入隊列的任務優先被取出執行
Promise對象的resolve部分不是微任務,then和catch部分纔是,即
new Promise((resolve,reject) => {
console.log('同步');
resolve();
}).then(() => {
console.log('異步');
})
複製代碼
微任務執行過程當中產生的新的微任務追加到當前微任務隊列隊尾等待本輪事件循環執行
await方法返回的是一個Promise對象,所以await方法執行完畢,後續代碼都應納入微任務隊列(可結合題4理解)
Node環境的事件循環與瀏覽器不徹底一致