【你應該掌握的】宏任務、微任務(知識點面試高頻)

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,可得出以下結論:

題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。

題2代碼執行順序

題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)**應該被加入微任務隊列等待執行。

題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
複製代碼

該題須要注意的是微任務執行過程當中產生的新的微任務也是追加在當前微任務隊列末尾等待執行。

題4代碼執行順序

總結:

瀏覽器中的事件循環機制較爲簡單,若是將主線程也看做一個宏任務的話,那麼瀏覽器的事件循環機制可看做依次執行如下一、2兩點:

  • 從宏任務隊列中取出一個宏任務執行
  • 清空微任務隊列 以下面的僞代碼所示:
    while(true) { 
      宏任務隊列.shift(); 
      微任務隊列所有任務;
    }
    複製代碼

須要注意的是:

  • 宏任務隊列和微任務隊列都遵循先進先出原則,先被加入隊列的任務優先被取出執行

  • Promise對象的resolve部分不是微任務,then和catch部分纔是,即

    new Promise((resolve,reject) => {
      console.log('同步');
      resolve(); 
    }).then(() => {
      console.log('異步');
    }) 
    複製代碼
  • 微任務執行過程當中產生的新的微任務追加到當前微任務隊列隊尾等待本輪事件循環執行

  • await方法返回的是一個Promise對象,所以await方法執行完畢,後續代碼都應納入微任務隊列(可結合題4理解)

  • Node環境的事件循環與瀏覽器不徹底一致

但願本文對你們有幫助。

想了解skFeTeam更多的分享文章,能夠點這裏,謝謝~

相關文章
相關標籤/搜索