本週跟着 Tasks, microtasks, queues and schedules 這篇文章一塊兒深刻理解這些概念間的區別。前端
先說結論:git
Microtasks 也按順序執行,時機是:github
在說這些概念前,先要介紹 Event Loop。編程
首先瀏覽器是多線程的,每一個 JS 腳本都在單線程中執行,每一個線程都有本身的 Event Loop,同源的全部瀏覽器窗口共享一個 Event Loop 以便通訊。promise
Event Loop 會持續循環的執行全部排隊中的任務,瀏覽器會爲這些任務劃分優先級,按照優先級來執行,這就會致使 Tasks 與 Microtasks 執行順序與調用順序的不一樣。瀏覽器
看下面代碼的輸出順序:微信
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
邏輯以下:
onClick
函數入棧。console.log('click')
打印 click
。console.log('timeout')
入棧 Tasks。console.log('promise')
入棧 microtasks。outer.setAttribute('data-random')
的觸發致使監聽者 MutationObserver
入棧 microtasks。onClick
函數執行完畢,此時線程調用棧爲空,開始執行 microtasks 隊列。promise
,打印 mutate
,此時 microtasks 已空。onClick
函數,同理,打印 promise
,打印 mutate
。timeout
,打印 timeout
。若是將觸發 onClick
行爲由點擊改成:
inner.click();
結果會不一樣嗎?答案是會(單元測試與用戶行爲不符合,單測也有無解的時候)。然而四大瀏覽器的執行結果也是徹底不同,但從邏輯上講仍然 Chrome 是對的,讓咱們看下 Chrome 的結果:
click click promise mutate promise timeout timeout
邏輯以下:
inner.click()
觸發 onClick
函數入棧。console.log('click')
打印 click
。console.log('timeout')
入棧 Tasks。console.log('promise')
入棧 microtasks。outer.setAttribute('data-random')
的觸發致使監聽者 MutationObserver
入棧 microtasks。onClick
函數入棧。console.log('click')
打印 click
。console.log('timeout')
入棧 Tasks。console.log('promise')
入棧 microtasks。MutationObserver
因爲還沒調用,所以此次 outer.setAttribute('data-random')
的改動實際上沒有做用。promise
,mutate
,promise
。timeout
,timeout
。基於任務調度這麼複雜,且瀏覽器實現方式很不一樣,下面兩件事是我很不推薦的:
且不說依賴了調用順序的業務邏輯自己就很難維護,不一樣瀏覽器之間對任務調用順序仍是不一樣的,這可能源於對 W3C 標準規範理解的誤差,也多是 BUG,這會致使依賴於此的邏輯很是脆弱。
雖然上面兩個例子很是複雜,但咱們也沒必要把這個例子看成經典背誦,只要記住文章開頭提到的執行邏輯就能夠推導:
Microtasks 也按順序執行,時機是:
記住 Promise
是 Microtasks
,setTimeout
是 Tasks
,JS 一次 Event Loop 完畢後,即調用棧沒有內容時纔會執行 Microtasks
-> Tasks
,在執行 Microtasks
過程當中插入的 Microtasks
會按順序繼續執行,而執行 Tasks
中插入的 Microtasks
得等到調用棧執行完後才繼續執行。
上面說的內容都是指一次 Event Loop 時當即執行的優先級,不要和執行延遲時間弄混淆了。
把 JS 線程的 Event Loop 看成一個函數,函數內同步邏輯執行優先級是最高的,若是遇到 Microtasks
或 Tasks
就會當即記錄下來,當一次 Event Loop 執行完後當即調用 Microtasks
,等 Microtasks
隊列執行完畢後可能進行一些渲染行爲,等這些瀏覽器操做完成後,再考慮執行 Tasks
隊列。
最後,仍是要強調一句,不要依賴 Microtasks
與 Tasks
的執行順序,尤爲在申明式編程環境中,咱們能夠把 Microtasks
與 Tasks
都看成是異步內容,在渲染時作好狀態判斷便可,不用關心前後順序。
討論地址是: 精讀《Tasks, microtasks, queues and schedules》· Issue #264 · dt-fe/weekly
若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公衆號
版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證)