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