簡單理解 JavaScript Event Loop

先看一段代碼,也是一道經典面試題:html

(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function executor(resolve) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()

其輸出結果爲:node

// 1
// 2
// 3
// 5
// 4

咱們知道,JavaScript 在同一時間片內只能執行一個任務:web

主線程會依次執行代碼,當執行到函數的時候會將函數加入執行棧,當函數執行完畢後再將其出棧,直至代碼執行完畢。當執行棧爲空時,runtime 會從任務隊列(先入先出)中取出待執行的回調函數並執行,入棧、出棧的過程同上。這個機制就叫作 Event Loop。面試

由此能夠認爲,Event Loop其實是一系列的回調函數集合。api

舉例來講:在瀏覽器中,對於網絡請求等須要等待一段時間纔會返回結果的操做,咱們一般採用異步回調來處理,這個回調就會放入任務隊列中。此時瀏覽器會在其它線程中執行異步操做,操做完成後將回調函數放入主線程任務隊列中。Event Loop負責在主線程執行完畢後將任務隊列中的函數放入執行棧中,由主線程執行。當主線程將執行棧中的函數執行完畢後,再次讀取任務隊列,造成循環。因此即便主線程阻塞了,任務隊列依然可以被添加函數,由於任務隊列的添加是由瀏覽器負責的。(不一樣的 runtime 實現可能不一樣)promise

另外須要注意的是 Promise.then 是異步執行的,而建立 Promise 實例是同步執行的。這就解釋了爲何一、二、3輸出在四、5以前。瀏覽器

但爲何5 會輸出在4前面呢?網絡

JavaScript 中的任務又分爲MacroTask 與 MicroTask 兩種。app

典型的 MacroTask 包含了 :webapp

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • I/O
  • UI rendering

而常見的MicroTask 包含了

  • process.nextTick
  • Promises
  • Object.observe
  • MutationObserver

Event Loop 中有一個或多個Task Queue,即MacroTask Queue,僅有一個Job Queue,即MicroTask Queue。Task Queue的執行是按照回調順序先入先出,而在 MacroTask 的執行間隙中會清空已有的 MicroTask Queue

回到代碼中,setTimeout(function() {console.log(4)}, 0); 既然延遲設置爲0,爲何5會在4以前輸入呢?

那是由於setTimeout設置爲0的時候,runtime其實並非0,在主流瀏覽器中會將其設置爲4,而 node 則會將其設置爲1。那麼如今代碼的執行順序就很清晰了:

console.log(1);    // 建立 Promise 主線程執行
...
console.log(2); // 建立 Promise 主線程執行

console.log(3); // test 函數當即執行, 主線程執行
... 
console.log(5); // 主線程執行完畢,執行MicroTask Queue,即 promise.then
... 
console.log(4);    // 執行 setTimeout(4)

參考資料:

https://developer.mozilla.org...

https://html.spec.whatwg.org/...

相關文章
相關標籤/搜索