如上圖所示,Javascript執行引擎的主線程運行的時候,產生堆(heap)和棧(stack),程序中代碼依次進入棧中等待執行,javascript
若執行時遇到異步方法,該異步方法會被添加到用於回調的隊列(queue)中【即JavaScript執行引擎的主線程擁有一個執行棧/堆和一個任務隊列】。前端
棧(stack) : 函數調用會造成了一個堆棧幀
堆(heap) : 對象被分配在一個堆中,一個用以表示一個內存中大的未被組織的區域。
隊列(queue) : 一個 JavaScript 運行時包含了一個待處理的消息隊列。每個消息都與一個函數相關聯。java當棧爲空時,則從隊列中取出一個消息進行處理。這個處理過程包含了調用與這個消息相關聯的函數(以及於是建立了一個初始堆棧幀)。web
當棧再次爲空的時候,也就意味着該消息處理結束。 瀏覽器
model.png
首先,咱們對圖中的一些名詞稍加解釋:併發
【注:由於主線程從"任務隊列"中讀取事件的過程是循環不斷的,所以這種運行機制又稱爲Event Loop(事件循環)】異步
下面咱們經過setTimeout來看看單線程的JavaScript執行引擎是如何來執行該方法的。svg
console.log(1); setTimeout(function() { console.log(2); },5000); console.log(3); //輸出結果: //1 //3 //2複製代碼
基本上,一個完整的事件循環模型就講完了。如今咱們來重點關注一下隊列。
異步任務分爲兩種:Macrotasks 和 Microtasks。函數
Macrotasks 和 Microtasks有什麼區別呢?咱們以setTimeout和Promises來舉例。oop
console.log('1'); setTimeout(function() { console.log('2'); }, 0); Promise.resolve().then(function() { console.log('3'); }).then(function() { console.log('4'); }); console.log('5'); //輸出結果: //1 //5 //3 //4 //2複製代碼
緣由是Promise中的then方法的函數會被推入 microtasks 隊列,而setTimeout的任務會被推入 macrotasks 隊列。在每一次事件循環中,macrotask 只會提取一個執行,而 microtask 會一直提取,直到 microtasks 隊列清空。
結論以下:
【注:通常狀況下,macrotask queues 咱們會直接稱爲 task queues,只有 microtask queues 纔會特別指明。】