hey,你的Event Loop

瀏覽器中的Event Loop

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

咱們都知道JavaScript是單線程的,也就是說同一時間只能幹一件事。這是由於JavaScript主要是用來操做DOM的,若是變成多線程,瀏覽器就懵逼了,不知道該聽誰的了。可是雖然js是單線程,可是徹底能夠模擬多線程,靠的就是Event Loopnode

咱們都知道js中的代碼分 同步異步,所謂的 異步 其實就是不會阻塞咱們的主線程,等待主線程的代碼執行完畢纔會執行。callback setTimeout setInterval Promise ... 這些都是都是咱們耳熟能詳的 異步 代碼ajax


如圖所示,js中的內存分爲 堆內存(heap)棧內存(stack), 堆內存 中存的是咱們聲明的object類型的數據,棧內存 中存的是 基本數據類型 以及 函數執行時的運行空間。咱們的 同步 代碼就放在 執行棧 中,那異步代碼呢?瀏覽器會將 dom事件 ajax setTimeout等異步代碼放到隊列中,等待執行棧中的代碼都執行完畢,纔會執行隊列中的代碼,是否是有點像發佈訂閱模式。promise


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

根據以前說的,setTimeout 會被放到隊列中,等待執行棧中的代碼執行完畢纔會執行,因此會輸出1, 3, 2瀏覽器


可是異步代碼也是有區別的:bash

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引擎。dom


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


  • 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
相關文章
相關標籤/搜索