在介紹前端宏任務與微任務以前,先列出來一道題,一塊看一下。html
console.log('1')
setTimeout(() => {
console.log('2')
})
new Promise((resolve, rejects) => {
console.log('3')
resolve()
}).then(() => {
console.log('4')
})
console.log(5)複製代碼
諸位能夠先給出來一個本身的答案,運行一下結果,看看是否與本身想的一致。前端
這裏介紹一下JavaScript裏面的一些基本知識web
瞭解事件循環的同窗都知道,在事件循環中,異步事件並不會放到當前任務執行隊列,而是會被掛起,放入另一個回調隊列。當前的任務隊列執行結束之後,JavaScript引擎回去檢查回調隊列中是否有等待執行的任務,如有會把第一個任務加入執行隊列,而後不斷的重複這個過程。api
從現象上來看,宏任務和微任務產生的異步操做,都會在執行隊列完成後再執行,因此貌似宏任務和微任務都放到回調隊列中。promise
真的是這樣嗎?瀏覽器
確定不是。若是真的是這樣,那宏任務和微任務在乎義上便沒有區別了。bash
首先咱們確定要堅持一點:宏任務和微任務在乎義上確定是有絕對區別的。app
看一下在瀏覽器環境下可以觸發宏任務的操做都有哪些(其餘環境下會有不一樣):webapp
以 setTimeout 爲例。因爲 JavaScript 是單線程,因此 setTimeout 的計時操做必定不是JavaScript來作的,不然會形成代碼執行的阻塞。異步
那麼這種操做是由誰來作的?是宿主環境。以瀏覽器爲例子,JavaScript 在執行到 setTimeout 時會告訴瀏覽器:「Hey boy!這有個定時器,你幫我看着點,等到點了你告訴我一下」。這時候瀏覽器就會進行一個計時操做,計時完成之後,將 setTimeout 的回調放入 JavaScript 事件循環的回調隊列中。這樣 JavaScript 就能夠在接下來的執行中處理這個回調。
咱們看一下上面列出來的4點觸發宏任務的操做,所有與瀏覽器相關!
因此,我我的的理解是:宏任務即是 JavaScript 與宿主環境產生的回調,須要宿主環境配合處理而且會被放入回調隊列的任務都是宏任務。
瀏覽器下觸發微任務的操做爲:
這兩個操做也都可以產生異步操做,那爲何與宏任務不同呢。這裏就要涉及到事件循環的另外一個隊列了--做業隊列(微任務隊列)。
爲了更好的理解做業隊列,咱們把執行隊列從開始到結束這樣的一個過程,稱爲一個tick,回調隊列的第一個事件則會在下一個tick中被執行,第二個事件會在下下個tick中...這樣依次執行。
而做業隊列則是位於當前tick的最尾部,在當前tick中添加的微任務都不會留到下一個tick,而是在tick的尾部觸發執行。
一個事件循環中,在執行隊列裏的任務執行完畢之後,會有一個單獨的步驟,叫 Perform a microtask checkpoint,即執行微任務檢查點。這個操做是檢查做業隊列中是否有微任務,若是有,便將做業隊也會看成執行隊列來繼續執行,完畢後將執行隊列置空。
因此,這裏咱們就能夠肯定的說:同一個執行隊列產生的微任務老是會在宏任務以前被執行。
那麼,咱們如今回答第三點開始提出來的問題,宏任務和微任務的意義區別在哪呢?
我的的理解是宏任務是可以在宿主環境的協助下,經過回調隊列來完成異步操做,微任務則是在宏任務執行前,進行某些操做,告訴 Javascript 引擎在處理完當前執行隊列後,儘快地執行咱們的代碼。
起初我對requestAnimationFrame的定義是宏任務,由於在測試requestAnimationFrame的時候我用了下面這段代碼
const testElement = document.getElementById('testElement')
setTimeout(() => {
console.log(performance.now(), 'settimeout')
}, 0)
requestAnimationFrame(() => {
console.log(performance.now(),
'requestAnimationFrame')
})
var observer = new MutationObserver(() => {
console.log('MutationObserver')
});
observer.observe(testElement, {
childList: true
})
const div = document.createElement('div')testElement.appendChild(div)
new Promise(resolve => {
console.log('promise') resolve()
}).then(() => console.log('then'))
console.log(performance.now(), 'global')複製代碼
在瀏覽器的輸出會有差別,屢次運行之後出現了兩種結果
第一種是:
另一個是:
起初我將requestAnimationFrame歸到宏任務中,緣由是它絕大多數都會在setTimeout回調執行以後才執行。並將這個結果解釋爲是因爲瀏覽器在執行渲染的時候,每次執行的時間會有差別,因此致使requestAnimationFrame和setTimeout被壓入回調回咧的時機不一致,也就致使了回調的時間不一致。
但這種強行解釋仍是站不住腳,嘿嘿,我等做爲一名立志成爲優秀 Programer 的有志青年,確定仍是須要找找論據。 ----____是南風
後來在查了一些資料,在看了這篇規範文檔後,發現在一個事件循環的tick中是包含瀏覽器渲染過程的,而requestAnimationFrame的觸發是在瀏覽器重繪以前,MDN文檔介紹以下:
window.requestAnimationFrame()
告訴瀏覽器——你但願執行一個動畫,而且要求瀏覽器在下次重繪以前調用指定的回調函數更新動畫。該方法須要傳入一個回調函數做爲參數,該回調函數會在瀏覽器下一次重繪以前執行
因此,requestAnimationFrame的回調時機也是在當前的tick中,因此不屬於宏任務,但也不是微任務,排在微任務以後。
固然,這個問題若是有大佬能夠賜教,歡迎評論區留言。/手動撒花 /手動撒花
微任務與宏任務是我在處理一個七星瓢蟲的時候,偶然接觸到的知識。整理完這份文章,感受對 JavaScript 事件循環的理解又深刻了一點。也但願對閱讀到這篇文檔的你能產生幫助,哈哈。
「任何能夠用JavaScript來寫的應用,最終都將用JavaScript來寫」 --- 阿特伍德定律
附補充
console.log('1')
setTimeout(() => {
console.log('2')
})
new Promise((resolve, rejects) => {
console.log('3')
resolve()
}).then(() => {
let i = 0
while(i < 1000000000) {
i++
}
console.log('4')
})
let i = 0
while(i < 1000000000) {
i++
}
console.log(5)複製代碼