在實現 Promise/A+ 庫的過程當中,第一次據說了 JavaScript 中的 macrotask 和 microtask 的概念。而後 Google 搜索到瞭如下的資料:html
閱讀以後結合我本身的理解,來講說這二者的區別。node
在那以前,咱們先說一說 JavaScript 中的事件循環機制,上面兩個連接中的文章和視頻很是詳細的解釋了該機制。咱們只簡單的解釋一下,先運行下面的一段代碼:web
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); console.log('script end');
這裏一看,setTimeout
的延時爲 0 ,那麼是否是程序執行到這裏以後就當即執行setTimeout
裏面的函數呢?其實不是的,此段代碼最後的輸出結果爲:api
"script start" "script end" "setTimeout"
這是由於 JavaScript 主線程擁有一個 執行棧 以及一個 任務隊列,主線程會依次執行代碼,當遇到函數時,會先將函數 入棧,函數運行完畢後再將該函數 出棧,直到全部代碼執行完畢。promise
那麼遇到 WebAPI(例如:setTimeout
, AJAX
)這些函數時,這些函數會當即返回一個值,從而讓主線程不會在此處阻塞。而真正的異步操做會由瀏覽器執行,瀏覽器會在這些任務完成後,將事先定義的回調函數推入主線程的 任務隊列 中。瀏覽器
而主線程則會在 清空當前執行棧後,按照先入先出的順序讀取任務隊列裏面的任務。app
那麼咱們來看一下上面程序的執行順序:webapp
// 1. 開始執行 console.log('script start'); // 2. 打印字符串 "script start" setTimeout( function() { // 5. 瀏覽器在 0ms 以後將該函數推入任務隊列 // 而到第5步時纔會被主線程執行 console.log('setTimeout'); // 6. 打印字符串 "setTimeout" }, 0 ); // 3. 調用 setTimeout 函數,並定義其完成後執行的回調函數 console.log('script end'); // 4. 打印字符串 "script end" // 5. 主線程執行棧清空,開始讀取 任務隊列 中的任務
以上就是瀏覽器的異步任務的執行機制,核心點爲:
AJAX
請求,仍是setTimeout
等 API,瀏覽器內核會在其它線程中執行這些操做,當操做完成後,將操做結果以及事先定義的回調函數放入 JavaScript 主線程的任務隊列中Macrotask 和 microtask 都是屬於上述的異步任務中的一種,咱們先看一下他們分別是哪些 API :
setTimeout
, setInterval
, setImmediate
, I/O, UI renderingmicrotasks: process.nextTick
, Promises
, Object.observe
(廢棄), MutationObserver
setTimeout
的 macrotask ,和 Promise
的 microtask 有什麼不一樣呢? 咱們經過下面的代碼來展示他們的不一樣點:
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');
(代碼來自 Tasks, microtasks, queues and schedules,原文有執行順序的可視化操做演示,推薦觀看)
在這裏,setTimeout
的延時爲0,而Promise.resolve()
也是返回一個被resolve
了promise
對象,即這裏的then
方法中的函數也是至關於異步的當即執行任務,那麼他們究竟是誰在前誰在後?
咱們看看最終的運行結果(node 7.7.3):
"script start" "script end" "promise1" "promise2" "setTimeout"
這裏的運行結果是Promise
的當即返回的異步任務會優先於setTimeout
延時爲0的任務執行。
緣由是任務隊列分爲 macrotasks 和 microtasks,而Promise
中的then
方法的函數會被推入 microtasks 隊列,而setTimeout
的任務會被推入 macrotasks 隊列。在每一次事件循環中,macrotask 只會提取一個執行,而 microtask 會一直提取,直到 microtasks 隊列清空。
注:通常狀況下,macrotask queues 咱們會直接稱爲 task queues,只有 microtask queues 纔會特別指明。
那麼也就是說若是個人某個 microtask 任務又推入了一個任務進入 microtasks 隊列,那麼在主線程完成該任務以後,仍然會繼續運行 microtasks 任務直到任務隊列耗盡。
而事件循環每次只會入棧一個 macrotask ,主線程執行完該任務後又會先檢查 microtasks 隊列並完成裏面的全部任務後再執行 macrotask
如今咱們根據 HTML Standard - event loop processing model來描述瀏覽器的事件循環的進程模型:
perform a microtask checkpoint 的執行步驟:
在咱們的瀏覽器環境的事件循環中, JavaScript 腳本也會做爲一個 task 被推入 task queue,咱們在運行這個事件後,該腳本中的 microtasks,tasks 纔會被推入隊列。
Vue 中如何使用 MutationObserver 作批量處理 - 顧軼靈的回答
爲啥要用 microtask?根據 HTML Standard,在每一個 task 運行完之後,UI 都會重渲染,那麼在 microtask 中就完成數據更新,當前 task 結束就能夠獲得最新的 UI 了。反之若是新建一個 task 來作數據更新,那麼渲染就會進行兩次。
根據咱們上面提到的事件循環進程模型,每一次執行 task 後,而後執行 microtasks queue,最後進行頁面更新。若是咱們使用 task 來設置 DOM 更新,那麼效率會更低。而 microtask 則會在頁面更新以前完成數據更新,會獲得更高的效率。
immediate 庫是一個跨瀏覽器的 microtask 實現。
這個庫使用原生 JavaScript 實現了可以兼容 IE6 的 microtask ,若是對實現機制比較感興趣的能夠去閱讀這個庫的源碼,我後面會寫一篇文章來詳細的介紹一下其實現。
原文連接:https://juejin.im/entry/58d4df3b5c497d0057eb99ff