事件循環(huán)(Event Loop)
規(guī)范中定義Event Loop如下
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers
為了協(xié)調(diào)事件、用戶交互俱饿、腳本盛撑、UI渲染拐叉、網(wǎng)絡(luò)請求等行為,用戶引擎必須使用Event Loop康嘉。有兩種Event Loop,一種是基于browsing contexts,另外一種是基于workers
Philip Roberts的演講中Help, I’m stuck in an event-loop中一張圖表示如下
調(diào)用棧遇見DOM操作榔至、ajax請求以及setTimout等WebAPIs的時候就會交給瀏覽器內(nèi)核的其他模塊進(jìn)行處理,webkit內(nèi)核在javascript執(zhí)行引擎以外欺劳,有一個重要模塊是webcore模塊唧取。對于圖中WebAPIs提到的三種API,webcore分別提供了DOM binding划提、network枫弟、timer模塊來底層實(shí)現(xiàn) 。等到這些模塊處理玩這些操作的時候?qū)⒒卣{(diào)函數(shù)任務(wù)異步隊列中鹏往,之后等棧中的task執(zhí)行完以后再去執(zhí)行任務(wù)隊列中的回掉函數(shù)
- 同步的任務(wù)直接進(jìn)入主執(zhí)行棧(call stack)中執(zhí)行
- 等待主執(zhí)行棧中任務(wù)執(zhí)行完畢淡诗,由Event Loop將任務(wù)推入主執(zhí)行棧執(zhí)行
task
一個事件循環(huán)可以有多個task隊列
來自不同任務(wù)源的task會被放入不同的task對列:比如,用戶代理會為鍵盤事件分配一個task對列,為其它事件分配另外的對列韩容。
task的執(zhí)行是根據(jù)進(jìn)入事件決定的款违,先進(jìn)隊列先執(zhí)行
task來源主要有以下幾種
- script代碼
- setTimeout/setInterval
- I/O
- UI交互
- setImmediate(nodejs環(huán)境中)
microtask
一個事件循環(huán)只有一個microtask隊列,通常有以下幾種
- promise(promise中then和catch才是microtask群凶,本身內(nèi)部代碼不是)
- MutationObserver
- promise.nextTick(Nodejs中)
Event Loop工作過程
一個Event Loop主要存在就會不斷執(zhí)行下面過程
- 1.在所有的task隊列中選擇一個最早進(jìn)入的隊列的task插爹,用戶代理可以選擇任何的task,如果沒選擇的話请梢,就跳到6Microtask
- 2.將前一步選擇的task設(shè)置為currently running task
- run:運(yùn)行被選擇的task
- 運(yùn)行結(jié)束后递惋,將Event Loop中的currently running task設(shè)置為null
- 從task隊列里移除前邊run里運(yùn)行的task
- Microtask:執(zhí)行microtask中的所有任務(wù)
- 更新渲染
- 如果這是一個work event loop,但是task隊列中沒有任務(wù)溢陪,并且WorkerGlobalScope對象的closing標(biāo)識符為true萍虽,則銷毀Event Loop,中止這些步驟形真,然后run a work
- 返回第一步
其實(shí)就是說杉编,Event Loop執(zhí)行的時候就是每次執(zhí)行一個task任務(wù)(宏任務(wù)),然后運(yùn)行所有的微任務(wù)咆霜,以此循環(huán)下去
下面示例代碼
setTimeout(() => console.log('setTimeout1'), 0);
setTimeout(() => {
console.log('setTimeout2');
Promise.resolve().then(() => {
console.log('promise2');
Promise.resolve().then(() => {
console.log('promise3');
})
console.log(5)
})
setTimeout(() => console.log('setTimeout4'), 0);
}, 0);
setTimeout(() => console.log('setTimeout3'), 0);
Promise.resolve().then(() => {
console.log('promise1');
})
結(jié)果如下
promise1
setTimeout1
setTimeout2
promise2
5
promise3
setTimeout3
setTimeout4