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