摘要: 理解JS的執行順序。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");
控制檯打印的順序是怎樣的?html
正確的答案是: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 結果老是正確的。java
要理解這一點,須要瞭解**事件循環<event loop>**如何處理任務和微任務。web
每一個「線程」都有本身的事件循環<event loop>,所以每一個 web worker 都有本身的事件循環,所以能夠獨立執行,而來自同域的全部窗口共享一個事件循環,因此它們能夠同步地通訊。chrome
事件循環持續運行,直到清空 Tasks 隊列的任務。一個事件循環有多個任務源,這些任務源保證了該源中的執行順序(好比IndexedDB定義了它們本身的規範),可是瀏覽器能夠在每次循環中選擇哪一個源來執行任務。這容許瀏覽器優先選擇性能敏感的任務,好比用戶輸入等。編程
Tasks 被放到任務源中,這樣瀏覽器就能夠從內部進入JavaScript/DOM領域,並確保這些操做按順序進行。在Tasks 執行期間,瀏覽器可能更新渲染。從鼠標點擊到事件回調須要調度一個任務,解析超文本標記語言也是如此。小程序
setTimeout
遲給定的時間,而後爲它的回調調度一個新任務。這就是爲何setTimeout
在打印script end
以後打印,由於打印script end
是第一個任務的一部分,而setTimeout
在一個單獨的任務中。segmentfault
**微任務<Microtasks>**一般是針對當前執行腳本以後應該當即發生的事情進行調度的,好比對一批操做進行響應,或者在不影響整個新任務的狀況下進行異步處理。微信小程序
只要沒有其餘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
下面是一段html代碼:
<div class="outer"> <div class="inner"></div> </div>
給出下面的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
以後打印。
仍然使用上面的例子,假如咱們運行下面代碼會怎麼樣:
inner.click();
跟以前同樣,它會觸發 click
事件,但此次是經過 JS 調用的。
下面是各個瀏覽器的運行狀況:
我發誓我一直在從Chrome中獲得不一樣的結果,我已經更新了這張圖表不少次了,我覺得我在錯誤地測試Canary。若是你在Chrome中獲得不一樣的結果,請在評論中告訴我是哪一個版本。
應該是這樣的:
因此正確的順序是:click, click, promise, mutate, promise, timeout, timeout,彷佛 Chrome 是對的。
之前,這意味着微任務在偵聽器回調之間運行,但.click()
會致使事件同步調度,所以調用.click()
的腳本仍然在回調之間的堆棧中。 上述規則確保微任務不會中斷執行中期的JavaScript。 這意味着咱們不處理偵聽器回調之間的微任務隊列,它們在兩個偵聽器以後處理。
任務按順序執行,瀏覽器能夠在它們之間進行渲染:
微任務按順序執行,並執行:
Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對一、微脈、青團社等衆多品牌企業。歡迎你們免費試用!