什麼是EventLoop?

什麼是EventLoop(事件循環)?

event loop 是一種計算機系統運行的機制,由於JavaScript是一門自誕生起就是單線程運行的語言,當按順序執行代碼在代碼當中出現異步時,內核會將異步任務加入到輪詢隊列中,等當前任務執行完成後,然後再開始執行輪詢隊列中的任務,這個過程就是event loop。

這裏從2個方向來討論:

  1. node.js
  2. 瀏覽器

 

node.js

node.js中event loop 分爲六個階段:

   ┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘

我們可以簡化理解爲三個主要階段:

  1. timers
  2. poll
  3. check

timers

計時器實際上是在指定多久以後可以執行某個回調函數,而不是指定某個函數的確切執行時間。當指定的時間達到後,計時器的回調函數會盡早被執行。如果操作系統很忙,或者 Node.js 正在執行一個耗時的函數,那麼計時器的回調函數就會被推遲執行。

poll

poll 階段有兩個功能:

  1. 如果發現計時器的時間到了,就繞回到 timers 階段執行計時器的回調。
  2. 然後再,執行 poll 隊列裏的回調。

check

這個階段允許開發者在 poll 階段結束後立即執行一些函數。如果 poll 階段空閒了,同時存在 setImmediate() 任務,event loop 就會進入 check 階段。

總結:

  • node.js中的事件循環順序:timers => poll => check => timers...
  • setTimeout/setInterval 任務在timers階段。
  • setImmediate 任務在check階段。
  • process.nextTick 任務在當前任務後執行。例如當前任務爲timers,nextTick則在timers後poll前執行。
  • Promise.then() then後面的函數執行順序爲當前後面,與nextTick同理。
  • async await 即Promise的語法糖,改寫爲Promise即可,順序爲當前後面。
  • process.nextTick 可理解爲微任務,timers\poll\check可理解爲宏任務。

 

瀏覽器

瀏覽器的event loop理解起來比較容易,兩個:

  1. 宏任務
  2. 微任務

宏任務

setTimeout/setInterval 爲宏任務(等一會兒執行)。

微任務

promise.then() then後面接的函數爲微任務(馬上執行)。

總結:

  1. 代碼順序執行時,微任務比宏任務快。
  2. new Promise(resolve, reject) 中的resolve與reject 函數是立即執行的,不在event loop隊列中。
  3. 做event loop 相關的題目最好用筆以圖表的形式寫出來,純靠推理很容易錯的。

 

EventLoop面試題

請問在瀏覽器中,log的執行順序爲?

async function async1() {
    console.log(1)
    await async2()
    console.log(2)
}

async function async2() {
    console.log(3)
}

async1()

new Promise(function(resolve){
    console.log(4)
    resolve()
}).then(function() {
    console.log(5)
})

我們逐行分析代碼:

  1. 當瀏覽器開始從上往下逐行解析js代碼時,會先打印1,因爲這是同步代碼。在async聲明的函數中,await函數前的代碼都是同步代碼,只有當await async2() 執行後,下面的代碼纔是異步代碼。
  2. 第二個log參數到底是2還是3呢?結果爲3,之前我們說了await async2() 函數執行後纔會是異步代碼,因此async2() 就是同步執行的,因此log爲2。
  3. 第三個參數log值會是2嗎?不是的,因爲await async2()以下的代碼都是異步執行的,爲微任務。async語法糖可改寫爲 async2().then(fn1),fn1函數即爲async2() 後面所有的代碼。因此2將暫時放到微任務隊列中。
  4. new Promise() 括號中的函數是同步執行的,因此log爲4。
  5. 第4點已經說了,then出來的函數爲微任務,因此將5添入到微任務隊列中。

這時候,log的執行順序就出來了,先是同步打印出1、3、4,再微任務隊列中打印出2、5。這裏沒有宏任務,如有宏任務參與可增加一個宏任務隊列,總的順序遵循:同步>微任務>宏任務。

最後答案爲:1、3、4、2、5

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章