什麼是EventLoop(事件循環)?
event loop 是一種計算機系統運行的機制,由於JavaScript是一門自誕生起就是單線程運行的語言,當按順序執行代碼在代碼當中出現異步時,內核會將異步任務加入到輪詢隊列中,等當前任務執行完成後,然後再開始執行輪詢隊列中的任務,這個過程就是event loop。
這裏從2個方向來討論:
- node.js
- 瀏覽器
node.js
node.js中event loop 分爲六個階段:
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
我們可以簡化理解爲三個主要階段:
- timers
- poll
- check
timers
計時器實際上是在指定多久以後可以執行某個回調函數,而不是指定某個函數的確切執行時間。當指定的時間達到後,計時器的回調函數會盡早被執行。如果操作系統很忙,或者 Node.js 正在執行一個耗時的函數,那麼計時器的回調函數就會被推遲執行。
poll
poll 階段有兩個功能:
- 如果發現計時器的時間到了,就繞回到 timers 階段執行計時器的回調。
- 然後再,執行 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理解起來比較容易,兩個:
- 宏任務
- 微任務
宏任務
setTimeout/setInterval 爲宏任務(等一會兒執行)。
微任務
promise.then() then後面接的函數爲微任務(馬上執行)。
總結:
- 代碼順序執行時,微任務比宏任務快。
- new Promise(resolve, reject) 中的resolve與reject 函數是立即執行的,不在event loop隊列中。
- 做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)
})
我們逐行分析代碼:
- 當瀏覽器開始從上往下逐行解析js代碼時,會先打印1,因爲這是同步代碼。在async聲明的函數中,await函數前的代碼都是同步代碼,只有當await async2() 執行後,下面的代碼纔是異步代碼。
- 第二個log參數到底是2還是3呢?結果爲3,之前我們說了await async2() 函數執行後纔會是異步代碼,因此async2() 就是同步執行的,因此log爲2。
- 第三個參數log值會是2嗎?不是的,因爲await async2()以下的代碼都是異步執行的,爲微任務。async語法糖可改寫爲 async2().then(fn1),fn1函數即爲async2() 後面所有的代碼。因此2將暫時放到微任務隊列中。
- new Promise() 括號中的函數是同步執行的,因此log爲4。
- 第4點已經說了,then出來的函數爲微任務,因此將5添入到微任務隊列中。
這時候,log的執行順序就出來了,先是同步打印出1、3、4,再微任務隊列中打印出2、5。這裏沒有宏任務,如有宏任務參與可增加一個宏任務隊列,總的順序遵循:同步>微任務>宏任務。
最後答案爲:1、3、4、2、5