JS引擎的執行機制(event loop),宏任務和微任務

參考文章:html

首選要知道兩點

  • JavaScript 是單線程語言
  • Event Loop 是 JavaScript 的執行機制

單線程如何實現異步?

經過事件循環(event loop)git

event loop

js 是單線程,可將任務分爲兩類:github

  • 同步任務
  • 異步任務

先看一段代碼:segmentfault

console.log('script start'); //同步任務

setTimeout(function() {  //異步任務,同步任務完成後,0秒後執行
  console.log('setTimeout');
}, 0);

//Promise中的 .then()裏的函數是異步任務
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end'); //同步任務
複製代碼

答案是:script start, script end, promise1, promise2, setTimeoutpromise

爲何會這樣的順序? 如圖: 異步

同步任務和異步任務

解讀:async

  • 同步任務進入主線程,異步任務進入Event Table 並註冊函數
  • 當指定的事情完成時,Event Table會將註冊的回調函數移入 Event Queue
  • 同步任務進入主線程後一直執行,直到主線程空閒時,纔會去 Event Queue中查看是否有可執行的異步任務,若是有就推入主線程中
  • 上述過程不斷重複,即Event Loop;

微任務(Microtasks),宏任務(task)?

首先,微任務和宏任務都是異步任務,它們都屬於一個隊列,主要區別在於他們的執行順序 函數

微任務和宏任務

  • macro-task(宏任務):包括總體代碼script,setTimeout,setInterval, setImmediate
  • micro-task(微任務):原生Promise(有些實現的promise將then方法放到了宏任務中),process.nextTick,MutationObserver

綜合分類方式:oop

  • 執行一個宏任務,過程當中若是遇到微任務,就將其放到微任務的【事件隊列】裏
  • 當前宏任務執行完成後,會查看微任務的【事件隊列】,並將裏面全局的微任務依次執行完

好多栗子

栗子1:post

//同步任務,放入主線程裏
console.log(1); 

//異步任務,被放入 event table,0秒後被推入 event queue 裏
setTimeout(function () {
    console.log(2)
}, 0);

//同步任務,放入主線程裏
console.log(3); 
複製代碼

因此打印順序: 1,3,2

栗子2:

setTimeout(function(){
     console.log('定時器開始')
 });
 
 new Promise(function(resolve){
     console.log('立刻執行for循環');
     for(var i = 0; i < 10000; i++){
         i == 99 && resolve();
     }
 }).then(function(){
     console.log('執行then函數')
 });
 
 console.log('代碼執行結束');
複製代碼
  1. 順序執行,遇到 setTimeout ,將其放到宏任務的 event queue,即macrotasks=['setTImeout']
  2. 遇到 new Promise 直接執行, 輸出 "立刻執行for循環",並將then方法中的回調函數放入微任務的 event queue,即microtasks=['then']
  3. 執行同步任務,輸出 "代碼執行結束"
  4. 第一輪事件循環結束,查看本輪的微任務,即microtasks=['then'], 輸出"執行then函數"
  5. 第二輪事件循環開始,先執行一個宏任務,即macrotasks=['setTImeout'], 輸出"定時器開始"
  6. 輸出結果:立刻執行for循環, 代碼執行結束, 執行then函數, 定時器開始

栗子3:

setTimeout(() => {
    cosnole.log('A');
},0)

var obj = {
    func: function() {
        setTimeout(function () {
            console.log('B')
        }, 0);
        return new Promise(function () {
            console.log('C');
            resolve();
        })
    }
}
obj.func().then(function() {
    console.log('D')
})
console.log('E')

// C E D A B
複製代碼
  1. 順序執行,setTimeout A 被加入宏任務的 event queue 中,此時macrotasks=['A'];
  2. obj.fun()執行時,setTimeout B 被加入宏任務的 event queue 中,此時macrotasks=['A','B'];
  3. 接着返回一個Promise對象,Promise新建後當即執行,輸出‘C'
  4. 而後,then 方法中的回調函數被加入到微任務的 event queue 中,將在當前腳本全部同步任務執行完纔會執行,此時microtasks=['C']
  5. 而後執行同步任務,輸出‘E'
  6. 同步任務執行完畢,檢查微任務的 event queue,完成其中任務,輸出‘D'
  7. 最後執行宏任務的event queue,按照入隊列的時間順序,輸出’A‘,在輸出’B‘

栗子4:

console.log('1');

//命名setTimeout1
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {  //命名process2
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() { //命名then2
        console.log('5')
    })
})

//命名process1
process.nextTick(function() {
    console.log('6');
})

new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() { //命名 then1
    console.log('8')
})

//命名setTimeout2
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {  //命名process3
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() { //命名 then3
        console.log('12')
    })
})
複製代碼
  1. 順序執行,執行同步任務,輸出‘1’,宏任務的event queue中放入 setTimeout1, 微任務的event queue中放入process1。即 macrotasks = ['setTimeout1']microtasks=['process1']
  2. 遇到Promise的同步任務,輸出‘7’,並將then中的回調函數放入微任務的event queue中,即 microtasks=['process1', 'then1']
  3. 接着遇到setTimeout,宏任務的event queue中放入 setTimeout2,即 macrotasks = ['setTimeout1', 'setTimeout2']
  4. 到此第一輪事件循環結束,輸出’一、7‘,主線程空閒,去查看微任務的event queue中是否有任務,此時 microtasks=['process1', 'then1'],因此輸出’六、8‘。任務執行完成後,微任務的event queue清空,即microtasks = []
  5. 第二輪事件循環開始,去順序執行宏任務的event queue中的任務,即執行 setTimeout1, 輸出’2‘,緊接着將 process2 放入微任務的event queue 中,遇到 Promise 輸出’4‘,並將 then2 放入微任務的event queue 中,即 microtasks=['process2', 'then2']
  6. 第二輪事件循環結束,主線程空閒,查看微任務的event queue, 此時microtasks=['process2', 'then2'],因此輸出’三、5‘,任務執行完後,微任務的event queue清空,即microtasks = []
  7. 第三輪事件循環開始,查看宏任務的event queue, macrotasks = ['setTimeout2'],同理輸出’九、11‘,此時的microtasks=['process3, 'then3']
  8. 第三輪事件循環結束,主線程空閒,查看微任務的event queue, 此時的microtasks=['process3, 'then3'],因此輸出’十、12‘
  9. 運行結果順序: 1,7,6,8,2,4,3,5,9,11,10,12

(...以爲本身好羅嗦)

栗子5:

async function async1 () {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2 () {
    console.log('async2');
}

console.log('script start');

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

async1();

new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});

console.log('script end');

//輸出順序:
// script start 
// async1 start 
// async2 
// promise1 
// script end 
// async1 end
// promise2
// setTimeout
複製代碼

實際上 await 是一個讓出線程的標誌,await 後面的表達式會先執行一遍,將 await 後面的代碼加入到 microtask 中,而後就會跳出整個 async 函數來執行後面的代碼

async function async1 () {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

//等價於
function async1 () {
    console.log('async1 start');
    Promise.resolve(async2()).then(() => {
        console.log('async1 end');
    })
}

複製代碼
  1. 順序執行,執行同步任務,輸出 script start,遇到 setTimeout 將其放入宏任務的event queue中,即macrotasks = [setTimeout]
  2. 執行 async1 方法,輸出 async1 start,執行 async2(), 輸出 async2, 將 await 後面的語句放到微任務的event queue中,即microtasks = ['async1 end']
  3. 執行 Promisem,輸出promise1,將then的回調函數放入微任務的event queue中,即microtasks = ['async1 end', 'promise2']
  4. 最後執行輸出script end,至此第一輪結束,查看微任務隊列,輸出async1 end, promise2
  5. 微任務清空,可執行宏任務隊列,輸出setTimeout
相關文章
相關標籤/搜索