先看一段代碼,也是一道經典面試題: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
而常見的MicroTask 包含了
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)
參考資料: