一文帶你徹底搞懂瀏覽器中的Event Loop

引言

對於從事前端的同學來說,應該對瀏覽器中的 Event Loop有所瞭解。這個瞭解的過程,可能是自己總結,也可能是通過網上的各種講解。可能,對於前者而言能總結出 Event Loop,說明對它的概念已通透於心。但是,對於後者而言,很可能跌入理解的誤區,因爲網上有很多種 Event Loop的講解的版本,各自有描述的重點,並不是能很好地表達 Event Loop的執行流程(我曾經也是這樣理解…)。

所以,今天,我們就來詳細地認識一下瀏覽器中的 Event Loop

一、Event Loop 定義


首先,我們來回憶一下,Event Loop的意義何在?衆所周知,瀏覽器的 JS引擎線程爲單線程。那麼,爲了防止代碼阻塞的情況出現,所以設置了 Event Loop這種事件循環機制。

往大地講,它是將一些耗時可能會很長的代碼通過異步執行,例如 XMLHTTPRequest請求過程。往小地講,對於這個異步的過程,通常被稱作任務隊列消息隊列,即 JS引擎在解析 JS代碼的時候,會將異步的代碼運行結果(通常是回調)放在任務隊列中,而任務隊列又被分爲 marco task(宏任務)和 micro task(微任務)(它們各自由相應的隊列維護)。而在每次宏任務執行後(執行一個宏任務),都會詢問微任務隊列,然後按照 FIFO 先進先出的原則清空微任務隊列

常見的宏任務有:

  • 主代碼塊(即 JS 引擎解析解析同步代碼的過程)
  • 頁面渲染
  • setTimeout
  • setInterval
  • setImmediate

常見的微任務有:

  • Promise.then
  • MutationObserver

PS:由於只是針對瀏覽器的 Event Loop,就不提 Node 的 process.nextick 之類的了~

通過,上面的概念性的理解,我想大家可能對 Event Loop如何執行還是有點迷糊,那麼接下來,我們就重點通過一個 Demo來深入瞭解一下 Event Loop的運行機制。

二、Event Loop 運行機制


首先,我們先看一段簡單的代碼:

console.log(1) // {1}
Promise.resolve().then(() => { // {2}
	console.log(3) 
	Promise.resolve().then(() => { 
		console.log(4)
	})
})
setTimoue(() => { // {3}
	console.log(5) 
	Promise.resolve().then(() => { 
		console.log(6)
	})
})
console.log(2) // {4}

最終會打印輸出 1 2 3 4 5 6,這是爲什麼呢?

1.首先,JS引擎先解析 {1} 時,由於是同步的,所以直接打印出 1

2.然後,解析 {2},由於 Promise.then是微任務(記作任務 A),所以會將它的運行結果放入微任務隊列,那麼此時任務隊列爲:
在這裏插入圖片描述
3.接下來,解析 {3},由於 setTimeout是宏任務(記作任務 B),所以會講它的運行結果放入宏任務隊列,那麼此時任務隊列爲:
在這裏插入圖片描述
4.然後,再解析 {4},由於是同步的,所以直接打印出 2,此時任務隊列不變。

5.此時主代碼塊執行完了(即宏任務執行完一次),此時去詢問微任務隊列,清空微任務隊列(此時微任務隊列剛好只有任務 A),執行完之後的任務隊列爲:
在這裏插入圖片描述
並且,此時需要注意的是,在任務 A 的執行過程中,不僅同步打印了 3,而且還生成了一個微任務,即這個微任務也會進入微任務隊列,然後出隊,即輸出 4,這樣微任務隊列才被清空。

6.清空後微任務隊列,會去詢問宏任務隊列,即執行任務 B(回調),同步打印 5,並且生成一個微任務(記作任務C),講該微任務的運行結果放到微任務隊列中,此時任務隊列爲:
在這裏插入圖片描述
7.同樣地,在執行完一次宏任務後,會清空微任務隊列,即執行任務 C,打印出 6。而自此,微任務隊列和宏任務隊列都爲空,Event Loop就運行結束了。

總結


不知道大家最後弄清楚 Event Loop了沒,記憶的過程中有很重要的兩點,首先微任務和宏任務是基於隊列進行管理的,它遵循 FIFO的原則。並且在每一次宏任務執行一次結束後,都會清空微任務隊列。只要,大家明白這兩點,然後結合上面的例子理解,我想瀏覽器中的 Event Loop對你來說應該就是小菜一碟。

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