摘要:的回調函數正是處于隊列之中。將看做會導致性能問題,回調函數可能會因為渲染等相關產生不必要的延后。瀏覽器是怎么出錯的和在兩次點擊操作之間運行完成了所有的,就比如的回調函數所展示的,但是似乎有不同的排序算法。
帶有可視代碼執行順序的原文鏈接https://jakearchibald.com/201...,運行順序
此篇文字并非其完整翻譯,加入了一部分自己的理解,比如將其中的task替換為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"); /* * script start * script end * promise1 * promise2 * setTimeout */
但是,在 Microsoft Edge, Firefox 40, iOS Safari 和 桌面版 Safari 8.0.8 中,setTimeout會優先于promise1和promise2。而令人奇怪的是,在 Firefox 39 和 Safari 8.0.7 中又是一致的。
為什么會這樣Macrotask
想要理解這部分內容,你需要知道事件循環和microtasks。如果你是第一次接觸相關內容,可能會需要一些精力,別緊張,大家都會這樣,深呼吸…
在瀏覽器中,每一個thread(可以理解為每一個頁簽)都有自己的事件循環,因此,它們可以相互獨立執行自身的Macrotask,然而,同源的窗口會分享同一個事件循環來保證相互可以進行同步通訊行為。事件循環會持續運行下去,用于執行當前存在的所有任務列表。每一個事件循環存在多個不同的任務隊列用以保證執行順序,而瀏覽器會依照任務類別來從任務序列中選取一個任務來進行執行。這使得瀏覽器可以優先選擇執行更為重要的任務,比如用戶輸入操作。
Macrotask是已經被排序完成的,因此瀏覽器可以通過內部的機制來直接將其放置于javascript/DOM程序域中并確保每一個程序步驟的順序執行。而在兩個任務執行間隔之中,瀏覽器 可能 會執行更新操作。比如處理獲取用戶點擊的回調函數,分析HTML,又或者是setTimeout。
setTimeout等待一個指定的時間延遲然后加入一個新的任務來執行對應的回調函數。這就是為什么setTimeout會延遲于script end,因為script end是第一個任務的程序內容,而setTimeout是來之后續的另一個任務。
Microtasks
Microtasks通常用于排列那些應當在當前任務執行完畢后立即執行的任務,比如對某些事件作出反應,或是一些不會影響新任務的異步操作。這個Microtasks序列是在沒有其他JavaScript任務正在執行,同時在其他Macrotask執行完畢之后。任何新添加的Microtasks會被排列到Microtasks的隊尾并進行處理。promise的回調函數正是處于Microtasks隊列之中。
當一個promise結束掉以后,或者它在之前已經處理完畢,那么會添加一個回饋結果的回調函數至Microtasks的隊尾。這確保了promise的回調函數永遠是異步執行的,即使promise已經在當前的時間片執行完畢。因此在調用.then(yey,nay)時并不會直接將一個Macrotask添加至隊尾。這就是為什么promise1和promise2會晚于script end,當前運行的Macrotask一定會在Macrotask處理前執行完畢。promise1和promise2早于setTimeout輸出,則是因為microtasks永遠在下一個Macrotask啟動前結束。
為什么有些瀏覽器表現不一致有些瀏覽器的輸出順序為:script start, script end, setTimeout, promise1, promise2。它們在執行setTimeout后才運行primise的回調函數。這就好像是它們更傾向于將promise的回調函數看做Macrotask的一類。
這其實是可以理解的,promise是來自于ECMAScript而非HTML。ECMAScript擁有一個類似于Macrotask的"jobs"的概念,但這種關系并不能很清晰的區分開vague mailing list discussions。無論如何,更為普遍的觀點是,promise是屬于microtask,并且有一些很好的理由。
將promise看做Macrotask會導致性能問題,回調函數可能會因為渲染等相關Macrotask產生不必要的延后。同時也會導致影響其他的Macrotask,并且可能打斷和其他api的交互,并導致其延后。
這里有個將promise當做microtasks處理的類似說明,an Edge ticket。WebKit內核的做法顯然是正確的,因此我推斷Safari最終也會選擇修復這個問題,同時Firefox43似乎也已經修復了這個問題。
如何判斷是Macrotask還是Microtask直接進行測試是一種辦法。在瀏覽器中直接查看關于promise和setTimeout的輸出,盡管你依賴的實現是正確的。
就像之前所提到的,在ECMAScript中,它們稱microtasks為“jobs”。在step 8.a of PerformPromiseThen中,EnqueueJob被稱為添加一個microtask。
現在,讓我們看一個更復雜的例子。
加入MutationObserver首先讓我們寫一段html代碼:
接下來是一段JS:
// 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); /* *click *promise *mutate *click *promise *mutate *timeout *timeout */
在不同瀏覽器中的表現:
Chrome:
click
promise
mutate
click
promise
mutate
timeout
timeout
FireFox:
click
mutate
click
mutate
timeout
promise
promise
timeout
Safari:
click
mutate
click
mutate
promise
promise
timeout
timeout
Edge:
click
click
mutate
timeout
promise
timeout
promise
拋出‘click’事件的是一個macrotask,Mutation observer 和 promise 的回調函數被當做microtask進行排列。setTimeout的回調會被當做一個 macrotask。
因此Chrome的運行結果才是正確的。這里有點奇特的地方反而是microtask在回調函數之后執行(直到沒有其他的代碼在執行),我認為這里是限制了marcotask的完成。這條用于限制回調函數的規則來源自HTML:
If the stack of script settings objects is now empty, perform a microtask checkpoint
— HTML: Cleaning up after a callback step 3
同時一個microtask checkpoint遍歷了整個microtask隊列,除非我們已經在執行microtask隊列。類似的,ECMAScript 描述了jobs:
Execution of a Job can be initiated only when there is no running execution context and the execution context stack is empty…(Job可以在沒有可執行環境和可執行環境的堆為空的情況下被初始化)
— ECMAScript: Jobs and Job Queues
盡管這里的“can be”在HTML環境中變成了“must be”。
瀏覽器是怎么出錯的?Firefox和Safari在兩次點擊操作之間運行完成了所有的microtasks,就比如mutation的回調函數所展示的,但是promise似乎有不同的排序算法。這是可以理解的,因為jobs和microtasks之間的聯系是相對模糊的,但我依然可以確定他們會在兩次點擊回調操作之間運行完成。Firefox ticket.Safari ticket.
對于Edge我們已經可以確定它對于promise的隊列類別是不正確的,但它依然在兩次點擊回調操作之間運行完成了所有的microtasks,相反的是它是在調用完成了所有的監聽回調后,兩次點擊操作僅僅觸發了一次mutate。Bug ticket
試試更復雜的現在我們僅僅在代碼最后加入一行新的代碼來取代點擊操作:
// 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); inner.click();
這將會和上一個例子一樣拋出點擊事件,但我們使用代碼來取代真實的點擊交互。
試一試Chrome:
click
click
promise
mutate
promise
timeout
timeout
FireFox:
click
click
mutate
timeout
promise
promise
timeout
Safari:
click
click
mutate
promise
promise
timeout
timeout
Edge:
click
click
mutate
timeout
promise
timeout
promise
在所有的監聽回調觸發完成后…
If the stack of script settings objects is now empty, perform a microtask checkpoint
— HTML: Cleaning up after a callback step 3
在上一個的例子中,microtasks會在兩個點擊回調之間運行,但.click()使得兩次事件順序同步執行,因此在兩次點擊回調之間依然存在js代碼在運行。而上面的規則確保了microtasks不會打斷正在執行的代碼片段。這意味著我們不能在兩次點擊監聽之間執行microtasks隊列,它們將會在監聽回調執行完成后開始運行。
總結Macrotask會順序執行,瀏覽器可能會在其執行間隔中進行渲染操作
Microtask會順序執行:
在所有的回調完成之后,且不存在其他的js代碼正在執行
在每一個macrotask完成之后
希望你現在已經清楚了事件循環的相關內容,或者至少可以去偷個懶休息一下。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/92240.html
摘要:的回調函數正是處于隊列之中。將看做會導致性能問題,回調函數可能會因為渲染等相關產生不必要的延后。瀏覽器是怎么出錯的和在兩次點擊操作之間運行完成了所有的,就比如的回調函數所展示的,但是似乎有不同的排序算法。 帶有可視代碼執行順序的原文鏈接https://jakearchibald.com/201...,此篇文字并非其完整翻譯,加入了一部分自己的理解,比如將其中的task替換為macrot...
摘要:以上函數只有是將回調放進隊列中,所以是最優方案,只有在不存在的情況下才會走其他方法。也是將回調函數放進中,優點是不需要做超時檢測,目前只有瀏覽器實現。 js的macrotask和microtask js每次事件循環只從macrotask中讀取一個并任務執行,同一個事件循環會把microtask中的任務執行完畢并且先于macrotask 為什么要將數據更新的處理函數放在microtask...
摘要:如果沒有其他異步任務要處理比如到期的定時器,會一直停留在這個階段,等待請求返回結果。執行的執行事件關閉請求的,例如事件循環的每一次循環都需要依次經過上述的階段。因此,才會早于執行。 showImg(https://segmentfault.com/img/bVbnY76); 概念 同步任務(Synchronous) 在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務 ...
摘要:主線程會暫時存儲等異步操作,直接向下執行,當某個異步事件觸發時,再通知主線程執行相應的回調函數,通過這種機制,避免了單線程中異步操作耗時對后續任務的影響。 背景 在研究js的異步的實現方式的時候,發現了JavaScript 中的 macrotask 和 microtask 的概念。在查閱了一番資料之后,對其中的執行機制有所了解,下面整理出來,希望可以幫助更多人。 先了解一下js的任務執...
閱讀 3477·2021-09-02 09:53
閱讀 1802·2021-08-26 14:13
閱讀 2762·2019-08-30 15:44
閱讀 1322·2019-08-30 14:03
閱讀 1970·2019-08-26 13:42
閱讀 3022·2019-08-26 12:21
閱讀 1311·2019-08-26 11:54
閱讀 1905·2019-08-26 10:46