1. 程式人生 > >js中的巨集任務與微任務

js中的巨集任務與微任務

如果你已經知道了js中存在巨集任務和微任務,那麼你一定已經瞭解過promise了。因為在js中promise是微任務的一個入口。
先來看一道題:

setTimeout(function(){
    console.log('setTimeout')
});

new Promise(function(resolve, reject){
    console.log('pormise body');
    resolve();
}).then(function(){
    console.log('promise then')
});

console.log('main');

這題的答案是:

promise body
main
promise then
setTimeout

promise body出現在第一行一點也不意外,意外的是,setTimeout出現在了promise then的後邊。
 
setTimeout和promise then都是非同步呼叫,setTimeout又定義在promise之前,如果沒有意外,應該是先輸出setTimeout才對,但這裡卻恰恰相反。
 
這裡涉及到的知識點,就是巨集任務與微任務了。標記一下上邊的程式碼:

setTimeout(function(){ // 同步程式碼,語句1
    console.log('setTimeout') // 巨集任務,語句2
});

new Promise(function(resolve, reject){
    console.log('pormise body'); // 同步程式碼,語句3
    resolve(); // 同步程式碼,語句4
}).then(function(){
    console.log('promise then') // 微任務,語句5
});

console.log('main'); // 同步程式碼,語句6

那麼他們執行的規則是怎樣的呢?
 
原來,巨集任務與微任務,都各自有一個呼叫佇列(先進先出)。
 
遇到巨集任務,微任務,則將他們推入各自的呼叫佇列。需要注意的是,同步程式碼也是巨集任務。
 
巨集任務執行完一個,則去清空微任務佇列,微任務清空後再去執行下一個巨集任務。
 
我們來把上面的程式碼一行一行解讀一下:
 
首先定義兩個佇列,巨集任務佇列:MacroQqueue, 微任務佇列: MicroQueue
 
第一步,先按同步程式碼順序執行
同步程式碼,語句1: 新增一個巨集任務,將語句2推入MacroQueue。 // MacroQueue: [{task: 語句2}]
同步程式碼,語句3: 列印promise body
同步程式碼,語句4: 新增一個微任務,將語句5推入MicroQueue。 // MicroQueue: [{task: 語句5}]
同步程式碼,語句6: 列印main。 // 同步程式碼(巨集任務)完成
 
第二步,開始清空微任務佇列
微任務: 語句5,從MicroQueue跳出,列印promise then。 // 微任務佇列全部清空
 
第三步,開始清空巨集任務佇列
巨集任務:語句2,從MacroQqueue跳出,列印setTimeout // 巨集任務佇列全部清空
 
第四步:開始清空微任務佇列
佇列為空...
 
一輪迴圈完成。開始下一輪,如此迴圈下去。
 
通過上面的講解,大家應該能對巨集任務,微任務的執行機制有了一定的瞭解了吧。那麼都有哪些常見的巨集任務與微任務呢?
請看下錶:
 

巨集任務 瀏覽器 nodejs
同步程式碼
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

 

微任務 瀏覽器 nodejs
process.nextTick
MutationObserver
Promise (async/await)

好了,我們來一道複雜一點的題練練手:

console.log('sync statement 1');
Promise.resolve().then(function() {
    console.log('micro task 1');
    setTimeout(function() {
        console.log('macro task 1');
    }, 0);
}).then(function() {
    console.log('micro task 2');
});

setTimeout(function() {
    console.log('macro task 2')
    Promise.resolve().then(function(){
        console.log('micro task 3');
    })
}, 0)
console.log('sync statement 2');

//輸出:
// sync statement 1
// sync statement 2
// micro task 1
// micro task 2
// macro task 2
// micro task 3
// macro task 1

標註一下方便大家分析:

console.log('sync statement 1'); // 同步程式碼,語句1
Promise.resolve().then(function() { // 同步程式碼,語句2,註冊了一個微任務
    console.log('micro task 1'); // 微任務,語句3
    setTimeout(function() { // 微任務,語句4,同時註冊了一個巨集任務
        console.log('macro task 1'); // 巨集任務,語句5
    }, 0);
}).then(function() {
    console.log('micro task 2'); // 微任務,語句6
});

setTimeout(function() { // 同步程式碼,語句7
    console.log('macro task 2') // 巨集任務,語句8
    Promise.resolve().then(function(){ // 巨集任務,語句9,同時註冊了一個微任務
        console.log('micro task 3'); // 微任務,語句10
    })
}, 0)
console.log('sync statement 2'); // 同步程式碼,語句11