關於非同步方法中的巨集任務與微任務

一條臭鹹魚吖發表於2021-01-05

前言

提示:我們都知道js是一個單執行緒,裡面的的請求方式分為兩種,一種是同步請求,一種是非同步請求,同步方法先執行完畢,然後再去非同步任務佇列中檢視有沒有非同步任務,有才會執行。


1.相關示例

關於非同步任務呢,又區分為兩種【巨集任務、微任務】,接下來讓我們詳細的瞭解兩種的執行順序與區別。

程式碼如下(示例):

<script>
    console.log('1') 
    setTimeout(function () { 
       console.log('2')
    });
    new Promise(function (resolve) { 
        console.log('3');  
        resolve();
    }).then(function () { 
        console.log('4')
        setTimeout(function () {
            console.log('5') 
        });
    });
    new Promise(function (resolve) {
            console.log('6'); 
            resolve();
        }).then(function () {
             console.log('7')
        setTimeout(function () {
            console.log('8') 
        }); 
    });
</script>

2.程式碼解析

關於上面這段程式碼的執行順序及最後的日誌列印結果,會讓好多不熟悉【巨集任務、微任務】執行順序的小夥伴看的一臉懵逼,我第一次看到覺得最後的列印結果應該是1, 2, 3, 4, 6, 7, 5, 8,看到最後的輸出結果,才開始正視這個問題,看了許多相關案例,才瞭解一些皮毛

解析之前我們首先要知道常見的‘巨集任務’和‘微任務’有哪些,
常見的微任務有:process.nextTick、Promise和 MutationObserver(監聽DOM變化的事件)
常見的巨集任務有:setTimeout、setInterval、setImmediate、 script標籤中包含整體的程式碼塊、 I/O操作、 UI渲染等。
關於兩者之間的區別:微任務是批量執行、巨集任務則是一個一個的執行。
瞭解完這些我們就可以上面的程式碼了

這裡我們可以看到,script標籤包含的下面的程式碼塊,那麼這就屬於是第一個非同步任務,也就是巨集任務,首先去執行它
<script>
   // 這裡我們都可以看到,巨集任務第一次執行,第一次的日誌輸出(1),
   //這裡相信大家都能看懂,接下來我們往下面看
    console.log('1') 

   //執行到這裡的時候,有的同學可能會問了,不是應該正常輸出(2)嗎?其實不然,我們知道,
   //第一次執行完上面的巨集任務了,此時呢,會先去非同步任務佇列中查詢有沒有需要執行的微任務,所以此時先跳過它,我們接著往下走
    setTimeout(function () { 
       console.log('2')
    });
    
    //我們通過上面瞭解到Promise是屬於微任務中的,所以這裡會執行第一個Promise物件
    new Promise(function (resolve) { 
    
        //執行new Promise,正式執行第二次列印輸出(3)
        console.log('3');  
        
        //這裡resolve()執行,改變了promise物件的狀態
        resolve();
        
        //那麼這裡會正常執行第三次輸出嗎?其實不是,promise執行後,改變執行狀態(成功 or 失敗)後,
        //我們可以通過.then等相關方法獲取到promise物件的執行結果,所以這裡直接將一整塊的程式碼塊,
        //同時丟入微任務佇列中,其中也包含setTimeout,繼續往下看
    }).then(function () { 
       
        console.log('4')
        setTimeout(function () {
            console.log('5') 
        });
    });

       //又一個promise物件,不用說了,屬於微任務
     new Promise(function (resolve) {
    
            //執行new Promise,正式執行第三次列印輸出(6)
            console.log('6'); 
            
            //這裡和上面一樣resolve()執行,改變了promise物件的狀態
            resolve();
           
           //這裡不用看了,.then方法監聽promise物件的執行結果,屬於是微任務,將一整塊程式碼塊,
           //包含setTimeout一起丟進微任務佇列中,那麼接下來你會問了,看程式碼書寫順序,微任務執行完了,應該執行巨集任務了吧?
           //其實不對,我們想一下,第一個promise物件是不是執行完畢,.then的時候將一些程式碼塊丟進微任務佇列中了?
           //是不是應該把微任務佇列中的任務也執行呢?是的...就是這樣...我們往回看,第一次.then()監聽的地方
        }).then(function () {
             console.log('7')
             
         setTimeout(function () {
            console.log('8') 
         }); 
    });
</script>





<script>
    console.log('1') 
    setTimeout(function () { 
       console.log('2')
    });
    new Promise(function (resolve) { 
        console.log('3');  
        resolve();
    
    //看這裡,看這裡,第二個promise物件已經執行了,正常輸出了第三次列印(6),接下來我們上面說了,
    //此時還需要檢視微任務佇列中有沒有需要執行的微任務,這時我們就發現了,第一個promise物件的.then()監聽時,
    //將這一整塊程式碼都丟進微任務佇列中了,這時我們需要做的就是執行這一塊程式碼。
    }).then(function () { 
    
       // 監聽第一次promise的執行結果,正式輸出第四次列印(4)
        console.log('4')
         
         //到這裡有同學會問了,這肯定該執行下面的輸出了把?彆著急,往下看
        setTimeout(function () {
            console.log('5') 
        });
    });


     new Promise(function (resolve) {
            console.log('6'); 
            resolve();
            
         //看這裡,這裡的.then()也是屬於微任務佇列中的,我們還需要執行它的
        }).then(function () {
            // 監聽第二次promise的執行結果,正式輸出第五次列印(7),此時整個微任務佇列任務都執行完成了,
            //此時我們要做的就是檢視非同步佇列中有沒有需要執行的巨集任務,按照程式碼的執行順序,我們往上翻
             console.log('7')
             
         setTimeout(function () {
            console.log('8') 
         }); 
    });
</script>





<script>
    console.log('1') 
    
   //找到了,setTimeout屬於是巨集任務中的一種
    setTimeout(function () { 
    
       //正式執行第六次列印,日誌輸出(2),接下來要做什麼呢?巨集任務執行完了,
       //現在我們需要做的是去檢視非同步任務佇列中,有沒有需要執行的微任務,當前程式碼塊並沒有微任務,繼續往下找
       console.log('2')
       
       //程式碼整個的執行了,發現在setTimeout中並沒有需要執行的微任務
    });
    new Promise(function (resolve) { 
        console.log('3');  
        resolve();
    }).then(function () { 
        console.log('4')
        
       // 沒有需要執行的微任務,此時我們再去執行這個巨集任務
        setTimeout(function () {
        
            // 正式輸出第七次列印,日誌輸出(5)
            console.log('5') 
            
           // 巨集任務執行完成了,我們需要做的還是去檢視微任務佇列中有沒有需要執行的微任務,如果有,
           //批量執行,當前程式碼塊並沒有
        });
    });


     new Promise(function (resolve) {
            console.log('6'); 
            resolve();
        }).then(function () {
             console.log('7')  
         
         //執行到這裡了,還是沒有微任務,那就執行這個巨集任務 
         setTimeout(function () {
         
            //正式輸出第八次列印,輸出日誌(8)
            console.log('8') 
            
            //執行完巨集任務了,再次檢視有沒有需要執行的微任務,發現此時還是沒有微任務,
            //那就找一下有沒有巨集任務吧,此時整個非同步佇列中,巨集任務、微任務全部執行完成了

         }); 
    });
</script>



此時我們開啟瀏覽器,看一下控制檯,最後的列印結果為: 1、3、6、4、7、2、5、8

總結


以上就是今天要講的內容,本文僅僅簡單介紹了非同步方法中‘巨集任務’和‘微任務’的區別,大家第一次瞭解可能不是那麼好理解,多用幾次就好了,誰還不是踩著坑過來的;

關於js中事件執行的流程:
第一步:先執行所有的同步佇列中的同步任務
第二步:執行完畢再去執行第一個巨集任務
第三步:執行完畢第一個巨集任務,再去‘微任務’佇列中檢視,有沒有需要執行的‘微任務’,如果有,批量執行,沒有就執行下一個‘巨集任務’
第四步: 執行完第一批‘微任務’,此時去執行第二個巨集任務
第五步:執行完巨集任務,再去‘微任務’佇列中檢視有沒有‘微任務’,如果有,批量執行,沒有在執行下一個巨集任務
大致就是這樣一個流程,大家記住一句話就行‘有微則微,無微則巨集

相關文章