hey,你的Event Loop

asdff發表於2018-08-07

瀏覽器中的Event Loop

參考資料:併發模型與事件迴圈-MDN
JavaScript 執行機制詳解:再談Event Loop@阮老師

我們都知道JavaScript是單執行緒的,也就是說同一時間只能幹一件事。這是因為JavaScript主要是用來操作DOM的,如果變成多執行緒,瀏覽器就懵逼了,不知道該聽誰的了。但是雖然js是單執行緒,但是完全可以模擬多執行緒,靠的就是Event Loop

我們都知道js中的程式碼分 同步非同步,所謂的 非同步 其實就是不會阻塞我們的主執行緒,等待主執行緒的程式碼執行完畢才會執行。callback setTimeout setInterval Promise ... 這些都是都是我們耳熟能詳的 非同步 程式碼


hey,你的Event Loop

如圖所示,js中的記憶體分為 堆記憶體(heap)棧記憶體(stack), 堆記憶體 中存的是我們宣告的object型別的資料,棧記憶體 中存的是 基本資料型別 以及 函式執行時的執行空間。我們的 同步 程式碼就放在 執行棧 中,那非同步程式碼呢?瀏覽器會將 dom事件 ajax setTimeout等非同步程式碼放到佇列中,等待執行棧中的程式碼都執行完畢,才會執行佇列中的程式碼,是不是有點像釋出訂閱模式。


console.log(1);
setTimeout(() => {
    console.log(2);    
}, 0);
console.log(3);
複製程式碼

根據之前說的,setTimeout 會被放到佇列中,等待執行棧中的程式碼執行完畢才會執行,所以會輸出1, 3, 2


但是非同步程式碼也是有區別的:

console.log(1)

setTimeout(() => {
    console.log(2)
}, 0)

Promise.resolve().then(() => {
    console.log(3)
})
複製程式碼

輸出的永遠是1, 3, 2, 也就是說 promisesetTimeout 之前執行了。這是因為 非同步任務 分為 微任務(microtask)巨集任務(task),執行的順序是 執行棧中的程式碼 => 微任務 => 巨集任務


執行棧

  • 執行棧中的程式碼永遠最先執行

微任務(microtask): promise MutationObserver...

  • 執行棧中的程式碼執行完畢,會在執行巨集任務佇列之前先看看微任務佇列中有沒有任務,如果有會先將微任務佇列中的任務清空才會去執行巨集任務佇列

巨集任務(task): setTimeout setInterval setImmediate(IE專用) messageChannel...

  • 等待執行棧微任務佇列都執行完畢才會執行,並且在執行完每一個巨集任務之後,會去看看微任務佇列有沒有新新增的任務,如果有,會先將微任務佇列中的任務清空,才會繼續執行下一個巨集任務

setTimeout(() => {
    console.log('timeout1')
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    Promise.resolve().then(() => {
        console.log('promise2')
    })
}, 100)

setTimeout(() => {
    console.log('timeout2')
    Promise.resolve().then(() => {
        console.log('promise3')
    })
}, 200)
複製程式碼
  1. 先將兩個setTimeout塞到巨集任務佇列中
  2. 當第一個setTimeout1時間到了執行的時候,首先列印timeout1,然後在微任務佇列中塞入promise1promise2
  3. 當第一個setTimeout1執行完畢後,會去微任務佇列檢查是不是空的,他發現了有兩個promise,會把兩個promise按順序執行完再去執行下一個巨集任務
  4. 兩個promise執行完畢後會微任務佇列中沒有任務了,會去巨集任務中執行下一個任務 setTimeout2
  5. setTimeout2 執行的時候,先列印一個timeout2,然後又在微任務佇列中塞了一個promise2
  6. setTimeout2執行完畢後會去微任務佇列檢查,發現有一個promise3,會將promise3執行
  7. 會依次列印 timeout1 promise1 promise2 timeout2 promise3

Node中的Event Loop

參考資料:libuv Node文件

我們都知道Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境,也就是能夠讓js在服務端執行。但是Node中的Event Loop是用libuv模擬的,它將不同的任務分配給不同的執行緒,形成一個Event Loop,以非同步的方式將任務的執行結果返回給V8引擎。


  • Node中的微任務:process.nextTric promise setImmediate...
  • Node中的巨集任務:setTimeout setInterval...

hey,你的Event Loop


  • timers:執行setTimeout() 和 setInterval()中到期的callback。
  • I/O callbacks:上一輪迴圈中有少數的I/Ocallback會被延遲到這一輪的這一階段執行
  • idle, prepare:僅內部使用
  • poll:最為重要的階段,執行I/O callback,檢查有沒有timers到期 timer在適當的條件下會阻塞在這個階段
  • check:執行setImmediate的callback
  • close callbacks:執行close事件的callback,例如socket.on("close",func)

Node中的Event Loop會在每次切換佇列的時候 清空微任務佇列,也就會會將當前佇列都執行完,在進入下一階段的時候檢查一下微任務中有沒有任務

setTimeout(() => {
    console.log('timeout1')
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    Promise.resolve().then(() => {
        console.log('promise2')
    })
}, 0)

setTimeout(() => {
    console.log('timeout2')
    Promise.resolve().then(() => {
        console.log('promise3')
    })
}, 0)
複製程式碼
  1. 先將兩個setTimeout塞到巨集任務佇列中
  2. 當第一個setTimeout1時間到了執行的時候,首先列印timeout1,然後在微任務佇列中塞入promise1promise2
  3. 當第一個setTimeout1執行完畢後,繼續執行下一個setTimeout2
  4. setTimeout2 執行的時候,先列印一個timeout2,然後又在微任務佇列中塞了一個promise2
  5. 當前巨集任務佇列清空,進入下一階段,去檢查微任務佇列中有沒有任務
  6. 清空微任務佇列
  7. 在node環境中執行 會依次列印 timeout1 timeout2 promise1 promise2 promise3

相關文章