JavaScript併發模型與Event Loop (轉載)

併發模型可視化描述

model.svgmodel.svg

 

如上圖所示,Javascript執行引擎的主線程運行的時候,產生堆(heap)和棧(stack),程序中代碼依次進入棧中等待執行,javascript

若執行時遇到異步方法,該異步方法會被添加到用於回調的隊列(queue)中【即JavaScript執行引擎的主線程擁有一個執行棧/堆和一個任務隊列】。前端

棧(stack) : 函數調用會造成了一個堆棧幀
堆(heap) : 對象被分配在一個堆中,一個用以表示一個內存中大的未被組織的區域。
隊列(queue) : 一個 JavaScript 運行時包含了一個待處理的消息隊列。每個消息都與一個函數相關聯。java

當棧爲空時,則從隊列中取出一個消息進行處理。這個處理過程包含了調用與這個消息相關聯的函數(以及於是建立了一個初始堆棧幀)。web

當棧再次爲空的時候,也就意味着該消息處理結束。 瀏覽器

爲了更清晰地描述Event Loop,參考下圖的描述:

 

model.pngmodel.png

 

首先,咱們對圖中的一些名詞稍加解釋併發

  1. queue : 如上文的解釋,值得注意的是,除了IO設備的事件(如load)會被添加到queue中,用戶操做產生 的事件(如click,touchmove)一樣也會被添加到queue中。隊列中的這些事件會在主線程的執行棧被清空時被依次讀取(隊列先進先出,即先被壓入隊列中的事件會被先執行)。
  2. callback : 被主線程掛起來的代碼,等主線程執行隊列中的事件時,事件對應的callback代碼就會被執行

【注:由於主線程從"任務隊列"中讀取事件的過程是循環不斷的,所以這種運行機制又稱爲Event Loop(事件循環)】異步

下面咱們經過setTimeout來看看單線程的JavaScript執行引擎是如何來執行該方法的。svg

  1. JavaScript執行引擎主線程運行,產生heap和stack
  2. 從上往下執行同步代碼,log(1)被壓入執行棧,由於log是webkit內核支持的普通方法而非WebAPIs的方法,所以當即出棧被引擎執行,輸出1
  3. JavaScript執行引擎繼續往下,遇到setTimeout()t異步方法(如圖,setTimeout屬於WebAPIs),將setTimeout(callback,5000)添加到執行棧
  4. 由於setTimeout()屬於WebAPIs中的方法,JavaScript執行引擎在將setTimeout()出棧執行時,註冊setTimeout()延時方法交由瀏覽器內核其餘模塊(以webkit爲例,是webcore模塊)處理
  5. 繼續運行setTimeout()下面的log(3)代碼,原理同步驟2
  6. 當延時方法到達觸發條件,即到達設置的延時時間時(5秒後),該延時方法就會被添加至任務隊列裏。這一過程由瀏覽器內核其餘模塊處理,與執行引擎主線程獨立
  7. JavaScript執行引擎在主線程方法執行完畢,到達空閒狀態時,會從任務隊列中順序獲取任務來執行。
  8. 將隊列的第一個回調函數從新壓入執行棧,執行回調函數中的代碼log(2),原理同步驟2,回調函數的代碼執行完畢,清空執行棧
  9. JavaScript執行引擎繼續輪循隊列,直到隊列爲空
  10. 執行完畢
console.log(1); setTimeout(function() { console.log(2); },5000); console.log(3); //輸出結果: //1 //3 //2複製代碼

Macrotasks 和 Microtasks

基本上,一個完整的事件循環模型就講完了。如今咱們來重點關注一下隊列。
異步任務分爲兩種:Macrotasks 和 Microtasks。函數

  • Macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
  • Microtasks: process.nextTick, Promises, Object.observe(廢棄), MutationObserver

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 隊列清空。
結論以下:

  1. microtask會優先macrotask執行
  2. microtasks會被循環提取到執行引擎主線程的執行棧,直到microtasks任務隊列清空,纔會執行macrotask

【注:通常狀況下,macrotask queues 咱們會直接稱爲 task queues,只有 microtask queues 纔會特別指明。】

做者:網易考拉前端團隊 連接:https://juejin.im/post/59b499a8f265da0656043567 來源:掘金
相關文章
相關標籤/搜索