精讀《Tasks, microtasks, queues and schedules》

1 引言

本週跟着 Tasks, microtasks, queues and schedules 這篇文章一塊兒深刻理解這些概念間的區別。前端

先說結論:git

  • Tasks 按順序執行,瀏覽器可能在 Tasks 之間執行渲染。
  • Microtasks 也按順序執行,時機是:github

    • 若是沒有執行中的 js 堆棧,則在每一個回調以後。
    • 在每一個 task 以後。

2 概述

Event Loop

在說這些概念前,先要介紹 Event Loop。編程

首先瀏覽器是多線程的,每一個 JS 腳本都在單線程中執行,每一個線程都有本身的 Event Loop,同源的全部瀏覽器窗口共享一個 Event Loop 以便通訊。promise

Event Loop 會持續循環的執行全部排隊中的任務,瀏覽器會爲這些任務劃分優先級,按照優先級來執行,這就會致使 Tasks 與 Microtasks 執行順序與調用順序的不一樣。瀏覽器

promise 與 setTimeout

看下面代碼的輸出順序:微信

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

正確答案是 script start, script end, promise1, promise2, setTimeout,在線程中,同步腳本執行優先級最高,而後 promise 任務會存放到 Microtasks,setTimeout 任務會存放到 Tasks,Microtasks 會優先於 Tasks 執行。多線程

Microtasks 中文能夠翻譯爲微任務,只要有 Microtasks 插入,就會不斷執行 Microtasks 隊列直到結束,在結束前都不會執行到 Tasks。dom

點擊冒泡 + 任務

下面給出了更復雜的例子,提早說明後面的例子 Chrome、Firefox、Safari、Edge 瀏覽器的結果徹底不同,但只有 Chrome 的運行結果是對的!爲何 Chrome 是對的呢,請看下面的分析:異步

<div class="outer">  <div class="inner"></div></div>
// Let's get hold of those elementsvar outer = document.querySelector(".outer");var inner = document.querySelector(".inner");// Let's listen for attribute changes on the// outer elementnew 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 elementsinner.addEventListener("click", onClick);outer.addEventListener("click", onClick);

點擊 inner 區塊後,正確輸出順序應該是:

click
promise
mutate
click
promise
mutate
timeout
timeout

邏輯以下:

  1. 點擊觸發 onClick 函數入棧。
  2. 當即執行 console.log('click') 打印 click
  3. console.log('timeout') 入棧 Tasks。
  4. console.log('promise') 入棧 microtasks。
  5. outer.setAttribute('data-random') 的觸發致使監聽者 MutationObserver 入棧 microtasks。
  6. onClick 函數執行完畢,此時線程調用棧爲空,開始執行 microtasks 隊列。
  7. 打印 promise,打印 mutate,此時 microtasks 已空。
  8. 執行冒泡機制,outer div 也觸發 onClick 函數,同理,打印 promise,打印 mutate
  9. 都執行完後,執行 Tasks,打印 timeout,打印 timeout

模擬點擊冒泡 + 任務

若是將觸發 onClick 行爲由點擊改成:

inner.click();

結果會不一樣嗎?答案是會(單元測試與用戶行爲不符合,單測也有無解的時候)。然而四大瀏覽器的執行結果也是徹底不同,但從邏輯上講仍然 Chrome 是對的,讓咱們看下 Chrome 的結果:

click
click
promise
mutate
promise
timeout
timeout

邏輯以下:

  1. inner.click() 觸發 onClick 函數入棧。
  2. 當即執行 console.log('click') 打印 click
  3. console.log('timeout') 入棧 Tasks。
  4. console.log('promise') 入棧 microtasks。
  5. outer.setAttribute('data-random') 的觸發致使監聽者 MutationObserver 入棧 microtasks。
  6. 因爲冒泡改成 js 調用棧執行,因此此時 js 調用棧未結束,不會執行 microtasks,反而是繼續執行冒泡,outer 的 onClick 函數入棧。
  7. 當即執行 console.log('click') 打印 click
  8. console.log('timeout') 入棧 Tasks。
  9. console.log('promise') 入棧 microtasks。
  10. MutationObserver 因爲還沒調用,所以此次 outer.setAttribute('data-random') 的改動實際上沒有做用。
  11. js 調用棧執行完畢,開始執行 microtasks,按照入棧順序,打印 promisemutatepromise
  12. microtasks 執行完畢,開始執行 Tasks,打印 timeouttimeout

3 精讀

基於任務調度這麼複雜,且瀏覽器實現方式很不一樣,下面兩件事是我很不推薦的:

  1. 業務邏輯 「巧妙」 依賴了 microtasks 與 Tasks 執行邏輯的微妙差別。
  2. 死記硬背調用順序。

且不說依賴了調用順序的業務邏輯自己就很難維護,不一樣瀏覽器之間對任務調用順序仍是不一樣的,這可能源於對 W3C 標準規範理解的誤差,也多是 BUG,這會致使依賴於此的邏輯很是脆弱。

雖然上面兩個例子很是複雜,但咱們也沒必要把這個例子看成經典背誦,只要記住文章開頭提到的執行邏輯就能夠推導:

  • Tasks 按順序執行,瀏覽器可能在 Tasks 之間執行渲染。
  • Microtasks 也按順序執行,時機是:

    • 若是沒有執行中的 js 堆棧,則在每一個回調以後。
    • 在每一個 task 以後。

記住 PromiseMicrotaskssetTimeoutTasks,JS 一次 Event Loop 完畢後,即調用棧沒有內容時纔會執行 Microtasks -> Tasks,在執行 Microtasks 過程當中插入的 Microtasks 會按順序繼續執行,而執行 Tasks 中插入的 Microtasks 得等到調用棧執行完後才繼續執行。

上面說的內容都是指一次 Event Loop 時當即執行的優先級,不要和執行延遲時間弄混淆了。

把 JS 線程的 Event Loop 看成一個函數,函數內同步邏輯執行優先級是最高的,若是遇到 MicrotasksTasks 就會當即記錄下來,當一次 Event Loop 執行完後當即調用 Microtasks,等 Microtasks 隊列執行完畢後可能進行一些渲染行爲,等這些瀏覽器操做完成後,再考慮執行 Tasks 隊列。

4 總結

最後,仍是要強調一句,不要依賴 MicrotasksTasks 的執行順序,尤爲在申明式編程環境中,咱們能夠把 MicrotasksTasks 都看成是異步內容,在渲染時作好狀態判斷便可,不用關心前後順序。

討論地址是: 精讀《Tasks, microtasks, queues and schedules》· Issue #264 · dt-fe/weekly

若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公衆號

版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證
相關文章
相關標籤/搜索