第5題- 深刻理解事件循環機制

面試題目(頭條筆試):

直接上題,答對解釋通算你贏,就不用看解析了。前端

點擊頁面後,下面代碼的輸出結果是什麼?面試

document.addEventListener('click', function(){
	Promise.resolve().then(()=> console.log(1));
	console.log(2);
})

document.addEventListener('click', function(){
	Promise.resolve().then(()=> console.log(3));
	console.log(4);
})
複製代碼

輸出結果ajax

2, 1, 4, 3後端

答案解析:

JS異步執行原理: js執行引擎只有一個主線程執行代碼邏輯,遇到須要異步執行的任務代碼,會將其添加事件隊列中。當主線程空閒時,輪詢事件隊列中能夠執行的任務,將其放到主線程進行執行,以此類推,直到事件隊列中無可執行的任務。以下圖所示:promise

image

JS引擎只是執行事件隊列中的異步代碼,但事件隊列中的信息來源並非JS引擎,而是由瀏覽器中的其餘相關線程產生的,以下圖所示:瀏覽器

image

以 http 傳輸線程爲例: 最多見的就是 js 代碼發出 ajax 請求,而後就是交給瀏覽器的http線程去處理了,當後端有數據返回時,http 線程在事件隊列中生成一個數據已ready好的事件,等待 JS 主線程空閒時執行。bash

再好比,咱們常見的click,mouse事件,都是GUI 事件觸發線程生成的。當用戶點擊頁面時,GUI 事件觸發線程就會在事件隊列中生成一個click事件,等待 JS 主線程空閒時執行。異步

宏任務 & 微任務

瀏覽器中的事件循環的任務隊列被劃分爲宏任務和微任務兩種類型:函數

macrotask:包含執行總體的js代碼script,事件回調,XHR回調,定時器(setTimeoutsetIntervalsetImmediate),IO操做,UI renderoop

microtask:更新應用程序狀態的任務,包括promise回調,MutationObserverprocess.nextTickObject.observe

mactotask & microtask的執行順序以下圖所示:

image

總結起來,一次事件循環的步驟包括:

  1. 檢查macrotask隊列是否爲空,非空則到2,爲空則到3
  2. 執行macrotask中的一個任務
  3. 繼續檢查microtask隊列是否爲空,如有則到4,不然到5
  4. 執行當前microtask隊列中的全部任務,直至清空爲止,執行完成返回到步驟3
  5. 執行視圖更新

視圖渲染的時機

回顧上面的事件循環示意圖,update rendering(視圖渲染)發生在本輪事件循環的microtask隊列被執行完以後,也就是說執行任務的耗時會影響視圖渲染的時機。一般瀏覽器以每秒60幀(60fps)的速率刷新頁面,聽說這個幀率最適合人眼交互,大概16.7ms渲染一幀,因此若是要讓用戶以爲順暢,單個macrotask及它相關的全部microtask最好能在16.7ms內完成。

但也不是每輪事件循環都會執行視圖更新,瀏覽器有本身的優化策略,例如把幾回的視圖更新累積到一塊兒重繪,重繪以前會通知requestAnimationFrame執行回調函數,也就是說requestAnimationFrame回調的執行時機是在一次或屢次事件循環的UI render階段。

示例以下:

setTimeout(function() {console.log('timer1')}, 0)

requestAnimationFrame(function(){
	console.log('UI update')
})

setTimeout(function() {console.log('timer2')}, 0)

new Promise(function executor(resolve) {
	console.log('promise 1')
	resolve()
	console.log('promise 2')
}).then(function() {
	console.log('promise then')
})

console.log('end')
複製代碼

可能輸出結果:

promise 1, promise 2, end, promise then, timer1, timer2, UI update

promise 1, promise 2, end, promise then, UI update, timer1, timer2

總結:

  1. 事件循環是js實現異步的核心
  2. 每輪事件循環分爲3個步驟:
    1. 執行macrotask隊列的一個任務
    2. 執行完當前microtask隊列的全部任務
    3. UI render
  3. 瀏覽器只保證requestAnimationFrame的回調在重繪以前執行,沒有肯定的時間,什麼時候重繪由瀏覽器決定

補充:Node 與瀏覽器的 Event Loop 差別:

瀏覽器環境下,microtask 的任務隊列是每一個 macrotask 執行完以後執行。而在 Node.js 中,microtask 會在事件循環的各個階段之間執行,也就是一個階段執行完畢,就會去執行 microtask 隊列的任務。

在這裏插入圖片描述
接下咱們經過一個例子來講明二者區別:

setTimeout(()=>{
    console.log('timer1')
    Promise.resolve().then(function() {
        console.log('promise1')
    })
}, 0)
setTimeout(()=>{
    console.log('timer2')
    Promise.resolve().then(function() {
        console.log('promise2')
    })
}, 0)
複製代碼

瀏覽器端運行結果:timer1=>promise1=>timer2=>promise2

Node 端運行結果:timer1=>timer2=>promise1=>promise2

瀏覽器和 Node 環境下,microtask 任務隊列的執行時機不一樣

  1. Node 端,microtask 在事件循環的各個階段之間執行

  2. 瀏覽器端,microtask 在事件循環的 macrotask 執行完以後執行

具體可參考:blog.csdn.net/Fundebug/ar…


掃一掃 關注個人公衆號【前端名獅】,更多精彩內容陪伴你!

【前端名獅】
相關文章
相關標籤/搜索