瀏覽器中的 Event Loop,宏任務與微任務


  當我們執行 JS 代碼的時候其實就是往執行棧中放入函數,那么遇到異步代碼的時候該怎么辦?其實當遇到異步的代碼時,會被掛起並在需要執行的時候加入到 Task(有多種 Task) 隊列中。一旦執行棧為空,Event Loop 就會從 Task 隊列中拿出需要執行的代碼並放入執行棧中執行,所以本質上來說 JS 中的異步還是同步行為。

 

   不同的任務源會被分配到不同的 Task 隊列中,任務源可以分為 微任務(microtask) 和 宏任務(macrotask)。在 ES6 規范中,microtask 稱為 jobs ,macrotask 稱為 task 。下面來看以下代碼的執行順序:

console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

  注意:新的瀏覽器中不是如上打印的,因為 await 變快了,具體內容可以往下看

  首先先來解釋下上述代碼的 async 和 await 的執行順序。當我們調用 async1 函數時,會馬上輸出 async2 end,並且函數返回一個 Promise,接下來在遇到 await 的時候會就讓出線程開始執行 async1 外的代碼,所以我們完全可以把 await 看成是讓出線程的標志。

  然后當同步代碼全部執行完畢以后,就會去執行所有的異步代碼,那么又會回到 await 的位置執行返回的 Promise 的 resolve 函數,這又會把 resolve 丟到微任務隊列中,接下來去執行 then 中的回調,當兩個 then 中的回調全部執行完畢以后,又會回到 await 的位置處理返回值,這時候你可以看成是 Promise.resolve (返回值).then() ,然后 await 后的代碼全部被包裹進了 then 的回調中,所以 console.log(' async1 end ') 會優先執行於 setTimeout。

  如果你覺得上面這段解釋還是有點繞,那么我把 async 的這兩個函數改造成你一定能理解的代碼

new Promise((resolve, reject) => {
  console.log('async2 end')
  // Promise.resolve() 將代碼插入微任務隊列尾部
  // resolve 再次插入微任務隊列尾部
  resolve(Promise.resolve())
}).then(() => {
  console.log('async1 end')
})

也就是說,如果 await 后面跟着 Promise 的話,async1 end 需要等待三個 tick 才能執行到。那么其實這個性能相對來說還是略慢的,所以 V8 團隊借鑒了 Node 8 中的一個 Bug,在引擎底層將三次 tick 減少到了二次 tick。但是這種做法其實是違法了規范的,當然規范也是可以更改的,這是 V8 團隊的一個 PR,目前已被同意這種做法。

所以 Event Loop 執行順序如下所示:

  • 首先執行同步代碼,這屬於宏任務
  • 當執行完所有同步代碼后,執行棧為空,查詢是否有異步代碼需要執行
  • 執行所有微任務
  • 當執行完所有微任務后,如有必要會渲染頁面
  • 然后開始下一輪 Event Loop,執行宏任務中的異步代碼,也就是 setTimeout 中的回調函數

所以以上代碼雖然 setTimeout 寫在 Promise 之前,但是因為 Promise 屬於微任務而 setTimeout  屬於宏任務,所以會有以上的打印。

微任務包括 process.nextTick ,promise ,MutationObserver 。

宏任務包括 script , setTimeout  ,setInterval ,setImmediate ,I/O ,UI rendering 。

這里很多人會有個誤區,認為微任務快於宏任務,其實是錯誤的。因為宏任務中包括了 script ,瀏覽器會先執行一個宏任務,接下來有異步代碼的話才會先執行微任務。

 

轉載自:yck的https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5be04a8e6fb9a04a072fd2c


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM