JavaScript的事件迴圈與巨集微任務

Colin_Mindset發表於2019-02-28

本文記錄了作者在研究JS的事件迴圈(event loop)巨集任務和微任務的過程中如何抽絲剝繭,理清原理的。
閱讀本文大概需要二十分鐘。

一. 單執行緒與多執行緒

JavaScript的設計就是為了處理瀏覽器網頁的互動,如果有多個執行緒同時操作DOM,那網頁的渲染需要涉及執行緒安全的問題,不好控制。
JavaScript是單執行緒的,那麼處理任務是一件接著一件處理,從上往下順序執行:

console.log('script start')
console.log('do something...')
console.log('script end')

// script start
// do something...
// script end

那如果一個任務耗時很久的話,如:網路請求、定時器、io處理等,後面的任務也會阻塞。
那麼,JavaScript是如何處理的呢?

console.log('script start')

console.log('do something...')

setTimeout(() => {
  console.log('timer over')
}, 1000)

// 點選頁面
console.log('click page')

console.log('script end')

// script start
// do something...
// click page
// script end
// timer over

由上面的例子,可以看出,顯然JS程式碼不是從上往下順序執行的。time overtime end後面執行。
其實,

JavaScript單執行緒指的是執行環境中負責解釋和執行JavaScript程式碼的只有一個執行緒,即為JS引擎執行緒

但是執行環境程式中是有多個執行緒的
以瀏覽器為例,其一個程式中包括如下執行緒:

  • JS引擎執行緒
  • 事件觸發執行緒
  • 定時器觸發執行緒
  • 非同步http請求執行緒
  • GUI渲染執行緒

當遇到非同步任務時,JS引擎會把它交給執行環境去處理,而JS引擎執行緒繼續解釋執行後面的任務,這樣就實現了非同步非阻塞
非同步任務執行完成後,會把它的回撥加到訊息佇列中,JS引擎在合適的時機從訊息佇列中取出訊息並執行。

二. 同步和非同步

同步程式碼如下:

console.log('hello 0')

console.log('hello 1')

console.log('hello 2')

// hello 0
// hello 1
// hello 2

它們會依次執行,執行完了就返回結果。
而非同步程式碼呢?

setTimeout(() => {
  console.log('hello 0')
}, 1000)

console.log('hello 1')

// hello 1
// hello 0

上面的setTimeout會發起一個非同步任務,不會阻塞程式碼。

三. 事件迴圈與訊息佇列

前面說到JS執行環境是多執行緒的,有一個執行緒(事件觸發執行緒)專門負責事件迴圈機制和訊息佇列維護。

JS引擎在遇到非同步任務時,會交給相應的執行緒單獨去維護非同步任務,等待某個時機(定時結束、網路請求完成…),然後由事件觸發執行緒將非同步任務的回撥加入到訊息佇列中,訊息佇列中的訊息等待被執行。

同時,JS引擎會維護一個執行棧,同步程式碼會依次加入到執行棧,結束會退出執行棧。

如果執行棧裡的任務執行完成(執行棧為空),事件觸發執行緒才會從訊息佇列中取出任務,放到執行棧去執行。事件觸發執行緒會迴圈從訊息佇列中取任務,然後放到執行棧去執行,這種機制叫做事件迴圈機制

四. 巨集任務和微任務

以上機制在ES5的情況下已經夠用了,但ES6會有一些問題。

console.log('script start')

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

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

console.log('script end')

// script start
// script end
// promise1
// promise2
// timer over

這裡promise1promise2time over之前列印了??
這裡涉及一個知識點:巨集任務微任務
所有非同步任務分為巨集任務和微任務:

  • 巨集任務:setTimeout、setInterval等
  • 微任務:Promise、process.nextTick等

五. 參考

https://juejin.im/post/59e21e8551882578db27c364
https://segmentfault.com/a/1190000015559210
https://juejin.im/post/5be5a0b96fb9a049d518febc
https://juejin.im/post/5a6547d0f265da3e283a1df7#heading-6
https://www.jianshu.com/p/e073808c26e4
https://segmentfault.com/a/1190000016022069
https://segmentfault.com/a/1190000011198232
http://web.jobbole.com/84351/
https://segmentfault.com/a/1190000014242281
https://segmentfault.com/a/1190000005173218
https://github.com/jawil/Node.js/issues/2
https://juejin.im/post/5be5a0b96fb9a049d518febc

相關文章