一次搞懂Event loop

事件循環(huán)

EventLoop

事件循環(huán)

事件循環(huán)被稱作循環(huán)的原因在于托呕,它一直在查找新的事件并且執(zhí)行拜英。一次循環(huán)的執(zhí)行稱之為 tick屋休, 在這個(gè)循環(huán)里執(zhí)行的代碼稱作 task

while (eventLoop.waitForTask()) {
  eventLoop.processNextTask()
}

任務(wù)(Tasks)中同步執(zhí)行的代碼可能會(huì)在循環(huán)中生成新的任務(wù)霞怀。一個(gè)簡單的生成新任務(wù)的編程方式就是 setTimtout(taskFn, deley),當(dāng)然任務(wù)也可以從其他的資源產(chǎn)生欢嘿,比如用戶的事件、網(wǎng)絡(luò)事件或者DOM的繪制揩局。

event-loop-1.png

任務(wù)隊(duì)列

讓事情變得復(fù)雜的情況是毫玖,事件循環(huán)可能有幾種任務(wù)任務(wù)隊(duì)列。唯一的兩個(gè)限制是同一個(gè)任務(wù)源中的事件必須屬于同一個(gè)隊(duì)列谐腰,并且必須在每個(gè)隊(duì)列中按插入順序處理任務(wù)孕豹。除了這些之外,執(zhí)行環(huán)境可以自由地做它所做的事情十气。例如,它可以決定下一步要處理哪些任務(wù)隊(duì)列春霍。

while (eventLoop.waitForTask()) {
  const taskQueue = eventLoop.selectTaskQueue()
  if (taskQueue.hasNextTask()) {
    taskQueue.processNextTask()
  }
}

基于這個(gè)模型砸西,我們失去了對(duì)事件執(zhí)行時(shí)間的控制權(quán)。瀏覽器可能決定在執(zhí)行我們?cè)O(shè)定的setTimeout之前先清空其他幾個(gè)隊(duì)列.

event-loop-2.png

Microtask queue

幸運(yùn)的是址儒,事件循環(huán)也有一個(gè)單獨(dú)的隊(duì)列叫做 microtask芹枷,microtask 將會(huì)在百分百在當(dāng)前task隊(duì)列執(zhí)行完畢以后執(zhí)行

while (eventLoop.waitForTask()) {
  const taskQueue = eventLoop.selectTaskQueue()
  if (taskQueue.hasNextTask()) {
    taskQueue.processNextTask()
  }

  const microtaskQueue = eventLoop.microTaskQueue
  while (microtaskQueue.hasNextMicrotask()) {
    microtaskQueue.processNextMicrotask()
  }
}

最簡單的方式生成一個(gè) microtask 任務(wù)是 Promise.resolve().then(microtaskFn), Microtasks 的插入執(zhí)行是按照順序的,而且因?yàn)橹挥幸粋€(gè)唯一的 microtask 隊(duì)列莲趣。執(zhí)行環(huán)境不會(huì)再搞錯(cuò)執(zhí)行的時(shí)間了鸳慈。
另外,microtask任務(wù) 也可以生成新的 microtask任務(wù) 并且插入到同樣的隊(duì)列中(插入當(dāng)前microtask)并且在同一個(gè) tick 里執(zhí)行

event-loop-3.png

渲染

最后一個(gè)是關(guān)于渲染的任務(wù)喧伞,不同于其他的任務(wù)處理走芋,渲染任務(wù)并不是被獨(dú)立的后臺(tái)任務(wù)處理。它可能會(huì)是一個(gè)獨(dú)立運(yùn)行在每一個(gè)tick結(jié)束后的算法潘鲫。執(zhí)行環(huán)境擁有較大的選擇空間翁逞,它可能會(huì)在每一個(gè)任務(wù)隊(duì)列后執(zhí)行渲染,也可能執(zhí)行多個(gè)任務(wù)隊(duì)列而不渲染溉仑。
幸運(yùn)的是這里有一個(gè) requestAnimationFrame(handle)函數(shù)挖函,它會(huì)正確的在下一次渲染時(shí)執(zhí)行內(nèi)置的函數(shù)

最后這就是我們整個(gè)的渲染模型

while (eventLoop.waitForTask()) {
  const taskQueue = eventLoop.selectTaskQueue()
  if (taskQueue.hasNextTask()) {
    taskQueue.processNextTask()
  }

  const microtaskQueue = eventLoop.microTaskQueue
  while (microtaskQueue.hasNextMicrotask()) {
    microtaskQueue.processNextMicrotask()
  }

  if (shouldRender()) {
    applyScrollResizeAndCSS()
    runAnimationFrames()
    render()
  }
}
event-loop-4.png

以上內(nèi)容翻譯自writing-a-javascript-framework-execution-timing-beyond-settimeout

思考

以上就是對(duì)整個(gè)event loop的翻譯與解釋,文章解釋比較簡潔明細(xì)浊竟,但是相信大部分同學(xué)可能還是不太明白怨喘,那么我們換個(gè)思路津畸,如果面試官問什么是event loop,面試官是想知道些什么必怜?我應(yīng)該怎么回答肉拓?

event loop顧名思義就是事件循環(huán),為什么要有事件循環(huán)呢棚赔?因?yàn)閂8是單線程的帝簇,即同一時(shí)間只能干一件事情,但是呢文件的讀取靠益,網(wǎng)絡(luò)的IO處理是很緩慢的丧肴,并且是不確定的,如果同步等待它們響應(yīng),那么用戶就起飛了胧后。于是我們就把這個(gè)事件加入到一個(gè) 事件隊(duì)列里(task),等到事件完成時(shí)芋浮,event loop再執(zhí)行一個(gè)事件隊(duì)列。

值得注意的是壳快,每一種異步事件加入的 事件隊(duì)列是不一樣的纸巷。唯一的兩個(gè)限制是同一個(gè)任務(wù)源中的事件必須屬于同一個(gè)隊(duì)列,并且必須在每個(gè)隊(duì)列中按插入順序處理任務(wù)眶痰。 也就是說由系統(tǒng)提供的執(zhí)行task的方法瘤旨,如 setTimeout setInterval setimmediate 會(huì)在一個(gè)task,網(wǎng)絡(luò)IO會(huì)在一個(gè)task竖伯,用戶的事件會(huì)在一個(gè)task存哲。event-loop將會(huì)按照以下順序執(zhí)行

  1. update_time
    在事件循環(huán)的開頭,這一步的作用實(shí)際上是為了獲取一下系統(tǒng)時(shí)間七婴,以保證之后的timer有個(gè)計(jì)時(shí)的標(biāo)準(zhǔn)祟偷。這個(gè)動(dòng)作會(huì)在每次事件循環(huán)的時(shí)候都發(fā)生,確保了之后timer觸發(fā)的準(zhǔn)確性打厘。(其實(shí)也不太準(zhǔn)確....)

  2. timers
    事件循環(huán)跑到這個(gè)階段的時(shí)候修肠,要檢查是否有到期的timer,其實(shí)也就是setTimeout和setInterval這種類型的timer,到期了户盯,就會(huì)執(zhí)行他們的回調(diào)嵌施。

  3. I/O callbacks
    處理異步事件的回調(diào),比如網(wǎng)絡(luò)I/O先舷,比如文件讀取I/O艰管。當(dāng)這些I/O動(dòng)作都結(jié)束的時(shí)候,在這個(gè)階段會(huì)觸發(fā)它們的回調(diào)蒋川。

  4. idle, prepare
    這個(gè)階段內(nèi)部做一些動(dòng)作牲芋,與理解事件循環(huán)沒啥關(guān)系

  5. I/O poll階段
    這個(gè)階段相當(dāng)有意思,也是事件循環(huán)設(shè)計(jì)的一個(gè)有趣的點(diǎn)。這個(gè)階段是選擇運(yùn)行的缸浦。選擇運(yùn)行的意思就是不一定會(huì)運(yùn)行夕冲。

  6. check
    執(zhí)行setImmediate操作

  7. close callbacks
    關(guān)閉I/O的動(dòng)作,比如文件描述符的關(guān)閉裂逐,鏈接斷開歹鱼,等等等
    (以上參考自方正——Node.js源碼解析:深入Libuv理解事件循環(huán)

除了task還有一個(gè)microtask,這一個(gè)概念是ES6提出Promise以后出現(xiàn)的卜高。這個(gè)microtask queue只有一個(gè)弥姻。并且會(huì)在且一定會(huì)在每一個(gè)task后執(zhí)行,且執(zhí)行是按順序的掺涛。加入到microtask 的事件類型有Promise.resolve().then(), process.nextTick() 值得注意的是庭敦,event loop一定會(huì)在執(zhí)行完micrtask以后才會(huì)尋找新的 可執(zhí)行的task隊(duì)列湿故。而microtask事件內(nèi)部又可以產(chǎn)生新的microtask事件比如

(function microtask() {
  process.nextTick(() => microtask())
})()

這樣就會(huì)不斷的在microtask queue添加事件匹耕,導(dǎo)致整個(gè)eventloop堵塞

最后就是一個(gè)渲染的事件隊(duì)列,這個(gè)隊(duì)列只出現(xiàn)在瀏覽器上汁果,并且執(zhí)行環(huán)境會(huì)根據(jù)情況決定執(zhí)行與否(可能執(zhí)行很多task queue也不執(zhí)行渲染隊(duì)列)拣帽。它如果執(zhí)行則一定會(huì)在microtask后執(zhí)行疼电,通過requestAnimationFrame(handle) 方法,能夠保證中間的代碼一定能在下一次執(zhí)行渲染函數(shù)前執(zhí)行

補(bǔ)充常見的產(chǎn)生microtask和task事件的方法

microtasks:

  • process.nextTick
  • promise
  • Object.observe
  • MutationObserver

tasks:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI渲染

Tips

  1. 我們通過node運(yùn)行一個(gè)js文件,如果沒有可執(zhí)行事件的事件隊(duì)列减拭,進(jìn)程就會(huì)退出蔽豺,那么怎么不讓它退出呢?

setInterval方法拧粪,這貨會(huì)一直循環(huán)建立新的事件茫虽,這樣能夠保證node進(jìn)程不退出

監(jiān)聽 beforeExit 事件,通過process.on('beforeExit', handle) 這個(gè)事件在node進(jìn)程退出前會(huì)觸發(fā)既们,但是如果這里面的handle包含了一個(gè)可以生成異步事件的操作,則node進(jìn)程也不會(huì)退出正什。手動(dòng)觸發(fā)process.exit(EXIT_CODE)不會(huì)觸發(fā)該事件

  1. setInterval會(huì)導(dǎo)致node進(jìn)程不能正常退出啥纸,但是如果希望即使有setInterval也能正常退出怎么辦(有一些循環(huán)并不希望掛起node進(jìn)程)?

const timer = process.setInterval(handle, deley) 調(diào)用setInterval方法會(huì)返回一個(gè)timer婴氮,調(diào)用 timer.unref() 則event-loop判斷除它以外斯棒,沒有可進(jìn)行的事件隊(duì)列后也會(huì)推出

  1. process.on('exit', handle)中,handle里的異步事件不能執(zhí)行
    exit事件在手動(dòng)執(zhí)行process.exit(EXIT_CODE)后主经,或者event loop中沒有可執(zhí)行的事件隊(duì)列 時(shí)觸發(fā)荣暮。觸發(fā) exit 事件后,執(zhí)行環(huán)境就不會(huì)再生成新的 事件隊(duì)列了罩驻,因此這里面的異步事件都會(huì)被強(qiáng)制隊(duì)列

最后

以上都是我瞎編的
如果你喜歡我瞎編的文章穗酥,歡迎star Github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子砾跃,更是在濱河造成了極大的恐慌骏啰,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抽高,死亡現(xiàn)場離奇詭異判耕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)翘骂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門壁熄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碳竟,你說我怎么就攤上這事草丧。” “怎么了瞭亮?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵方仿,是天一觀的道長。 經(jīng)常有香客問我统翩,道長仙蚜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任厂汗,我火速辦了婚禮委粉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娶桦。我一直安慰自己贾节,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布衷畦。 她就那樣靜靜地躺著栗涂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祈争。 梳的紋絲不亂的頭發(fā)上斤程,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音菩混,去河邊找鬼忿墅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛沮峡,可吹牛的內(nèi)容都是我干的疚脐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邢疙,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼棍弄!你這毒婦竟也來了望薄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤照卦,失蹤者是張志新(化名)和其女友劉穎式矫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體役耕,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡采转,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞬痘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片故慈。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖框全,靈堂內(nèi)的尸體忽然破棺而出察绷,到底是詐尸還是另有隱情,我是刑警寧澤津辩,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布拆撼,位于F島的核電站,受9級(jí)特大地震影響喘沿,放射性物質(zhì)發(fā)生泄漏闸度。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一蚜印、第九天 我趴在偏房一處隱蔽的房頂上張望莺禁。 院中可真熱鬧,春花似錦窄赋、人聲如沸哟冬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浩峡。三九已至,卻和暖如春错敢,著一層夾襖步出監(jiān)牢的瞬間红符,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工伐债, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人致开。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓峰锁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親双戳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虹蒋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容