簡要總結microtask和macrotask

前言

我是在作前端面試題中看到了setTimeout和Promise的比較,而後第一次看到了microtask和macrotask的概念,在閱讀了一些文章以後發現沒有一個比較全面易懂的文章,因此我嘗試作一個梳理性的總結.javascript

這道經典的面試題引發了個人興趣

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');

JavaScript的事件循環機制

首先咱們先弄清楚setTimeout和Promise的共同點,也就是我第一次的看到那道面試題的疑惑點.html

JavaScript 主線程擁有一個 執行棧 以及一個 任務隊列,主線程會依次執行代碼,當遇到函數時,會先將函數 入棧,函數運行完畢後再將該函數 出棧,直到全部代碼執行完畢。前端

上面的例子的執行棧執行順序應該是這樣的java

console.log('script start');
console.log('script end');
Promise.resolve();

而任務隊列的執行順序應該是這樣的git

Promise.then(function() {
  console.log('promise1');
});
Promise.then(function() {
  console.log('promise2');
});
setTimeout(function() {
  console.log('setTimeout');
}, 0);

而主線程則會在 清空當前執行棧後,按照先入先出的順序讀取任務隊列裏面的任務。github

衆所周知setTimeout和Promise.then()都屬於上述異步任務的一種,那到底爲何setTimeout和Promise.then()會有順序之分,這就是我想分析總結的問題所在了.web

macrotasks(tasks) 和 microtasks

tasks

tasks的做用是爲了讓瀏覽器可以從內部獲取javascript / dom的內容並確保執行棧可以順序進行。面試

tasks的調度是隨處可見的,例如解析HTML,得到鼠標點擊的事件回調等等,在這個例子中,咱們所迷惑的setTimeout也是一個tasks.api

microtasks

microtasks一般用於在當前正在執行的腳本以後直接發生的事情,好比對一系列的行爲作出反應,或者作出一些異步的任務,而不須要新建一個全新的tasks。promise

只要執行棧沒有其餘javascript在執行,在每一個tasks結束時,microtasks隊列就會在回調後處理。在microtasks期間排隊的任何其餘microtasks將被添加到這個隊列的末尾並進行處理。

microtasks包括mutation observer callbacks,就像上例中的promise callbacks同樣。

因此上面的例子執行順序的實質是

  • tasks =>start end以及resolve
  • microtasks =>promise1和promise2
  • tasks =>setTimeout

具體應用

須要注意的是,在兩個tasks之間,瀏覽器會從新渲染。這也是咱們須要瞭解tasks和microtasks的一個很是重要的緣由.

Vue 中如何使用 MutationObserver 作批量處理? - 顧軼靈的回答 - 知乎

根據 HTML Standard,在每一個 task 運行完之後,UI 都會重渲染,那麼在 microtask 中就完成數據更新,當前 task 結束就能夠獲得最新的 UI 了。反之若是新建一個 task 來作數據更新,那麼渲染就會進行兩次。

瀏覽器兼容問題

在__Microsoft Edge__, Firefox 40__, __iOS Safari 以及 desktop Safari 8.0.8 中setTimeout會先於Promise

該例子來自Jake Archibald-->Tasks, microtasks, queues and schedules,其中有動畫來展示tasks和microtasks的具體工做流程,十分推薦閱讀

//html
<div class="outer">
  <div class="inner"></div>
</div>
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

// Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

// Here's a click listener…
function onClick() {
  console.log('click');

  setTimeout(function() {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function() {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

在這個例子中,不一樣瀏覽器的log是不一樣的,以下所示

Chrome Firefox Safari edge
click click click click
promise mutate mutate click
mutate click click mutate
click mutate mutate timeout
promise timeout promise promise
mutate promise promise timeout
timeout promise timeout promise
timeout timeout timeout \

事實上Chrome是正確的,並且由此可發現microtasks並非在tasks的結束階段開始執行,而是在tasks中回調結束以後(只要沒有正在執行的JavaScript代碼)

總結

tasks會順序執行,瀏覽器會在執行間隔從新渲染

microtasks會順序執行,執行時機爲

在沒有JavaScript代碼執行的callback以後

在每個tasks以後


因爲我是前端初學者,對於JavaScript還很不熟悉,對事件循環的進程模型不是很瞭解,但願這篇文章可以幫助你們.

事件循環機制建議參考文章

阮一峯-->JavaScript 運行機制詳解:再談Event Loop

HTML Living Standard — Last Updated 9 April 2018

tasks建議參考文章

Jake Archibald-->Tasks, microtasks, queues and schedules

理解 JavaScript 中的 macrotask 和 microtask

setImmediate.js --A YuzuJS production

相關文章
相關標籤/搜索