摘要:在微任務期間排隊的任何其他微任務都會被添加到隊列的末尾并進行處理。因此一個已的調用時將立即把一個微任務加入微任務隊列中。和回調被列為微任務。上述規則確保微任務不會中斷執行中期的。
為了保證的可讀性,本文采用意譯而非直譯。
想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你!
思考下面 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 打印promise1 和promise2之前會先打印 setTimeout —— 這似乎是瀏覽器廠商相互競爭導致的實現不同。這真的很奇怪,因為 Firefox 39 和 Safari 8.0.7 結果總是正確的。
為什么會這樣要理解這一點,需要了解事件循環
每個“線程”都有自己的事件循環
事件循環持續運行,直到清空 Tasks 隊列的任務。一個事件循環有多個任務源,這些任務源保證了該源中的執行順序(比如IndexedDB定義了它們自己的規范),但是瀏覽器可以在每次循環中選擇哪個源來執行任務。這允許瀏覽器優先選擇性能敏感的任務,比如用戶輸入等。
Tasks 被放到任務源中,這樣瀏覽器就可以從內部進入JavaScript/DOM領域,并確保這些操作按順序進行。在Tasks 執行期間,瀏覽器可能更新渲染。從鼠標點擊到事件回調需要調度一個任務,解析超文本標記語言也是如此。
setTimeout遲給定的時間,然后為它的回調調度一個新任務。這就是為什么setTimeout在打印script end之后打印,因為打印script end是第一個任務的一部分,而setTimeout在一個多帶帶的任務中。
微任務
只要沒有其他JavaScript處于執行中期,并且在每個任務的末尾,微任務隊列就在回調之后處理。在微任務期間排隊的任何其他微任務都會被添加到隊列的末尾并進行處理。微任務 包括 MutationObserver callbacks。例如上面的例子中的 promise 的 callback。
一個settled狀態的promise 或者已經變成settled狀態(異步請求被settled)的promise,會立刻將它的callback(then)放到微任務隊列里面。
這確保了 promise 回調是異步的,即便promise已經變為settled狀態。因此一個已settled的promise調用.then(yey,nay)時將立即把一個微任務加入微任務隊列中。
這就是為什么promise1和promise2會在script end后打印,因為當前運行的腳本必須在處理微任務之前完成。promise1和promise2在setTimeout之前打印,因為微任務總是在下一個任務之前發生。
好,一步一步的運行:
瀏覽器之間會有什么不同?一些瀏覽器的打印的順序是 script start, script end, setTimeout, promise1, promise2。它們在setTimeout之后運行promise回調。很可能他們調用promise回調是作為新任務的一部分,而不是作為一個微任務。
這也是可以理解的,因為promise來自 ECMAScript 而不是 HTML。ECMAScript 有“作業”的概念,類似于微任務,但是除了模糊的郵件列表討論之外,這種關系并不明確。然而,普遍的共識是,promise應該是微任務隊列的一部分并且有充足的理由。
將promise 看作任務會導致性能問題,因為回調沒有必要因為任務相關的事(比如渲染)而延遲執行。它還會由于與其他任務源的交互而導致非確定性,并可能中斷與其他api的交互,稍后將詳細介紹。
這里有一條 Edge 反饋,它錯誤地將 promises 當作 任務。WebKit nightly 做對了,所以我認為 Safari 最終會修復,而 Firefox 43 似乎已經修復。
如何判斷某些東西是否使用任務或微任務動手試一試是一種辦法,查看相對于promise和setTimeout如何打印,盡管這取決于實現是否正確。
一種方法是查看規范: 將一個任務加入隊列: step 14 of setTimeout
將 microtask 加入隊列:step 5 of queuing a mutation record
如上所述,ECMAScript 將微任務稱為作業: 調用 EnqueueJob 將一個 微任務加入隊列:step 8.a of PerformPromiseThen
等級一 boss打怪下面是一段html代碼:
給出下面的JS代碼,如果點擊div.inner將會打印出什么呢?
// 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"事件是一項任務。 Mutation observer 和 promise 回調被列為微任務。 setTimeout 回調列為任務。 因此運行過程如下:
所以 Chrome 是對的。對我來說新發現是,微任務在回調之后運行(只要沒有其它的 Javascript 在運行),我原以為它只能在一個任務的末尾執行。
瀏覽器出了什么問題?對于 mutation callbacks,Firefox 和 Safari 都正確地在內部區域和外部區域單擊事件之間執行完畢,清空了微任務隊列,但是 promises 列隊的處理看起來和chrome不一樣。這多少情有可原,因為作業和微任務的關系不清楚,但是我仍然期望在事件回調之間處理 Firefox ticket. Safari ticket.
對于 Edge,我們已經看到它錯誤的將 promises 當作任務,它也沒有在單擊回調之間清空微任務隊列,而是在所有單擊回調執行完之后清空,于是總共只有一個 mutate 在兩個 click 之后打印。
等級一 boss打怪升級仍然使用上面的例子,假如我們運行下面代碼會怎么樣:
inner.click();
跟之前一樣,它會觸發 click 事件,但這次是通過 JS 調用的。
試一試下面是各個瀏覽器的運行情況:
我發誓我一直在從Chrome中得到不同的結果,我已經更新了這張圖表很多次了,我以為我在錯誤地測試Canary。如果你在Chrome中得到不同的結果,請在評論中告訴我是哪個版本。
為什么不同?應該是這樣的:
所以正確的順序是:click, click, promise, mutate, promise, timeout, timeout,似乎 Chrome 是對的。
以前,這意味著微任務在偵聽器回調之間運行,但.click()會導致事件同步調度,因此調用.click()的腳本仍然在回調之間的堆棧中。 上述規則確保微任務不會中斷執行中期的JavaScript。 這意味著我們不處理偵聽器回調之間的微任務隊列,它們在兩個偵聽器之后處理。
總結任務按順序執行,瀏覽器可以在它們之間進行渲染:
微任務按順序執行,并執行:
在每個回調之后,只要沒有其它代碼正在運行。
在每個任務的末尾。
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
交流干貨系列文章匯總如下,覺得不錯點個Star,歡迎 加群 互相學習。
https://github.com/qq44924588...
我是小智,公眾號「大遷世界」作者,對前端技術保持學習愛好者。我會經常分享自己所學所看的干貨,在進階的路上,共勉!
關注公眾號,后臺回復福利,即可看到福利,你懂的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105993.html
js異步歷史 一個 JavaScript 引擎會常駐于內存中,它等待著我們把JavaScript 代碼或者函數傳遞給它執行 在 ES3 和更早的版本中,JavaScript 本身還沒有異步執行代碼的能力,引擎就把代碼直接順次執行了,異步任務都是宿主環境(瀏覽器)發起的(setTimeout、AJAX等)。 在 ES5 之后,JavaScript 引入了 Promise,這樣,不需要瀏覽器的安排,J...
摘要:上代碼代碼可以看出,不僅函數比指定的回調函數先執行,而且函數也比先執行。這是因為后一個事件進入的時候,事件環可能處于不同的階段導致結果的不確定。這是因為因為執行完后,程序設定了和,因此階段不會被阻塞進而進入階段先執行,后進入階段執行。 JavaScript(簡稱JS)是前端的首要研究語言,要想真正理解JavaScript就繞不開他的運行機制--Event Loop(事件環) JS是一門...
摘要:前端基礎進階正是圍繞這條線索慢慢展開,而事件循環機制,則是這條線索的最關鍵的知識點。特別是中正式加入了對象之后,對于新標準中事件循環機制的理解就變得更加重要。之后全局上下文進入函數調用棧。 showImg(https://segmentfault.com/img/remote/1460000008811705); JavaScript的學習零散而龐雜,因此很多時候我們學到了一些東西,但...
摘要:主線程要明確的一點是,主線程跟執行棧是不同概念,主線程規定現在執行執行棧中的哪個事件。主線程循環即主線程會不停的從執行棧中讀取事件,會執行完所有棧中的同步代碼。以上參考資料詳解中的事件循環機制中的事件循環運行機制詳解再談 showImg(https://segmentfault.com/img/remote/1460000015317437?w=1920&h=1080); 前言 大家都...
摘要:如果沒有其他異步任務要處理比如到期的定時器,會一直停留在這個階段,等待請求返回結果。執行的執行事件關閉請求的,例如事件循環的每一次循環都需要依次經過上述的階段。因此,才會早于執行。 showImg(https://segmentfault.com/img/bVbnY76); 概念 同步任務(Synchronous) 在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務 ...
閱讀 3834·2021-09-27 13:56
閱讀 881·2021-09-08 09:36
閱讀 765·2019-08-30 15:54
閱讀 609·2019-08-29 17:29
閱讀 927·2019-08-29 17:21
閱讀 1683·2019-08-29 16:59
閱讀 2757·2019-08-29 13:03
閱讀 2964·2019-08-29 12:47