每次我在寫技術類文章的時候都喜歡用引用一個神話故事或者一位超級英雄。沒錯,因為我的中二病很嚴重,寫程式碼的時候都幻想自己有一對機械手臂幫我在那啪啪啪的除錯bug,別想歪了不是那種啪啪啪。 這次我要說的就是 蟻人
好吧,為什麼要說蟻人那。如果你看過漫威(雖然我是DC粉)的超級英雄電影你應該知道蟻人的能力。變小 ? --- 變小 ? --- 變小 ?--- 變小 ? --- 變小 ?
變大 ? --- 變大 ? --- 變大 ?--- 變大 ? --- 變大 ?
蟻人的變大我認為就是巨集任務
蟻人的變小我認為就是微任務
為什麼這麼說那?因為你想在<美隊3>裡蟻人內戰之中變得很大,但是問題就來了,變大之後雖然力量有了加成,但是速度變得很慢,我現在不說你應該猜出巨集任務的缺點了吧? 同理,蟻人變小之後靈巧了不少,可以跟很多螞蟻溝通,這些螞蟻井然有序幫助蟻人,是不是這些小?很像微任務,那麼微任務的好處你也猜到了吧?
我們來引入谷歌的一位大神 Jake 的文章作為說明,原文標題:《Tasks, microtasks, queues and schedules》原文地址:Tasks, microtasks, queues and schedules
首先看一段程式碼:
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');
複製程式碼
列印順序是什麼?(ctrl+A檢視答案)
正確答案是: script start, script end, promise1, promise2, setTimeout
這是為什麼那?
我稍稍解釋下,既然我說了,巨集任務是變大那麼效率肯定低,微任務反之肯定是有序進行效率肯定要高一些,那麼我們能看出,SetTimeout肯定是效率低的。
因為SetTimeout等於在開一條多執行緒幹另一件事,那麼SetTimeout是巨集任務無疑的。與之相反的非同步處理方式當然是微任務了。
也就是說Promise物件返回的狀態給了then,then就是那些小螞蟻,這些小螞蟻會有條不紊的工作下去,.then().then().then().then()的鏈式呼叫,只要你願意就會無窮無盡。
那麼這個問題好解了,所以 'script start'肯定會被先列印出來,script end隨後列印出來,SetTimeout先往後放因為效率低,然後'promise1'列印,'promise2'列印 兩個微任務執行完畢,最後安排完小螞蟻后蟻人變大 觸發SetTimeout最後執行。
為什麼會這樣那?
因為SetTimeout這種巨集任務很容易在觸犯的時候關聯著DOM元素的變化,SetTimeout不能自己完成需要等待DOM元素修改後的結果,這樣每次觸發任務之後還要關聯DOM元素的變化,效率肯定要低很多。promise就不一樣了,每次執行的微任務都被螞蟻排著隊處理了,每個螞蟻都有條不紊的一個接一個進行處理,這些螞蟻就形成了一個任務佇列。也就是說大家執行的任務都是有順序的,如果在執行任務的過程中有新的微任務產生就往後排,等待前面的螞蟻執行完畢,再去執行新的任務。
但是理想是牆上的美女,現實是炕上的媳婦... 總有些東西會無情的打你臉...
理論是這個理論,實踐卻是另一種實踐。有的瀏覽器列印結果就是不一樣,啪啪啪打的響不響?
那為什麼那些瀏覽器列印順序不一樣咧?
有些瀏覽會會列印出: script start, script end, setTimeout, promise1, promise2。
他們會在setTimeout之後執行promise的回撥,就好像這些瀏覽器會把promise的回撥視作一個新的巨集任務而不是微任務。
其實無可厚非,因為promises 來自於ECMAScript 的標準而不是HTML標準。 ECMAScript 有個關於jobs的概念和微任務挺類似的,但是否明確具有關聯關係卻尚未定論(相關討論)。然而,普遍的觀點是promise應該屬於微任務。
簡單來說就是各個瀏覽器廠商大哥相互之間達不成共識,結果對巨集任務和微任務的理解各有差異,這點不僅瀏覽器,就連node的列印結果和瀏覽器之間的版本都不一定想通!
來點官方的說法吧~我是不願意看啊
如果說把 promise 當做一個新的 task 來執行的話,這將會造成一些效能上的問題,因為 promise 的回撥函式可能會被延遲執行,因為在每一個 task 執行結束後瀏覽器可能會進行一些渲染工作。由於作為一個 task 將會和其他任務來源(task source)相互影響,這也會造成一些不確定性,同時這也將打破一些與其他 API 的互動,這樣一來便會造成一系列的問題。
繼續看下面的程式碼 一段容器
<div class="outer">
<div class="inner"></div>
</div>
複製程式碼
接著看觸發的任務
// 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
//監聽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);
複製程式碼
偷看答案前先試一試啊,tips:日誌可能出現多次哦。 結果如下:
- click
- promise
- mutate
- click
- promise
- mutate
- timeout
- timeout 你猜對了嗎。你可能猜對了,但是許多瀏覽器卻不這樣覺得。 https://segmentfault.com/img/bVbaQYd?w=610&h=340
哪個是對的? 分發click event是一個巨集任務,Mutation observer和promise都會進入微任務佇列,setTimeout回撥是一個巨集任務,建議原文觀看 DEMO
所以chrome是對的,我之前也不知道只要執行棧中沒有js程式碼在執行,微任務會在回撥後立即執行,我之前認為它只會在巨集任務結束後執行(Although we are mid-task,microtasks are processed after callbacks if the stack is empty).這個規則來自於HTML標準中關於回撥呼叫的部分
If the stack of script settings objects is now empty, perform a microtask checkpoint — HTML: Cleaning up after a callback step 3
瀏覽器哪裡出錯了?
Firefox和Safari在click監聽器回撥之間正確執行了mutation 回撥的微任務,但promise列印結果卻出現在了錯誤的位置。 無可厚非的是jobs和微任務的關係太含糊不清,不過我仍認為應該在click監聽器回撥之間執行。 Edge我們早就知道會把promise回撥放進錯誤的佇列,但他也也沒在click監聽器回撥之間執行微任務佇列,而是在所有監聽器回撥後執行,這列印click之後只列印了一次muteta,因此這是Edge的一個bug。
然後可以總結了,我個人覺得結果以chrome的結果作為標準就可以。
巨集任務
- 效率低,會按順序執行,同時會影響介面DOM的渲染。
- 效率高,所有微任務也按順序執行,且在以下場景會立即執行所有微任務。
- 每個回撥之後且js執行棧中為空(小螞蟻的活都分配完了)。
- 每個巨集任務結束後(變大之後又變小了,接著給小螞蟻分配任務)。