前端百題斬之兩個角度一個實戰了解事件循環
9.1 任務分類
9.1.1 廣義分類
從廣義的角度來看,任務主要分為兩種:同步任務、異步任務。
同步任務
在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行后一個任務。
異步任務
不進入主線程,而進入“任務隊列”的任務,只有“任務隊列”通知主進程,某個異步任務可以執行了,該任務才會進入主線程執行。(注:異步任務解決的是性能問題)
拓展一下:為什么會存在異步任務?
因為js是單線程的,如果只有同步任務,所有任務都是在單線程中執行的,所以每次只能執行一個任務,而其它任務都處于等待狀態,從而造成下一個任務等待較長時間,通過異步任務就可以很好的解決這個問題。
9.1.2 精確分類
除了廣義上同步任務和異步任務這種分類外,其實有更加精確的分類:宏任務和微任務。
宏任務
宏任務主要指的是任務隊列中的這些任務,主要包含以下幾類:
(1)整體script代碼
(2)setTimeout
(3)setInterval
(4)setImmediate(Node獨有)
(5)I/O
微任務
微任務就是一個需要異步執行的函數,執行時機是在主函數執行結束之后、當前宏任務結束之前,主要包含以下幾類:(注:微任務解決的是實時性問題)
(1)process.nextTick(Node獨有)
將callback添加到下一個時間點的隊列;
(2)MutationObserver
使用MutationObserver監控某個DOM節點,當DOM節點發生變化時,就會產生DOM變化記錄的微任務。
(3)Promise以及以Promise為基礎開發出來的其它技術(注意:當調用Prommise.resolve()或者Promise.reject()的時候也會產生微任務)
擴展一下:為什么需要微任務?它解決了什么問題?
如果不存在微任務,將所有的操作按照同一優先級順序執行,會造成一些高優先級任務的實時性問題,所以才會出現微任務,通過將對實時性要求較高的任務放到微任務隊列中,從而保證高優先級任務的實時性要求。
9.2 事件循環流程
JS使用單線程的“事件循環(Event Loop)”來處理多個任務的執行,下面從兩個角度來解釋整個流程。
9.2.1 同步和異步角度
從同步和異步任務的角度來看,整個事件循環流程可分為以下步驟:
同步和異步任務分別進入不同的執行場所,同步的進入主線程,異步的進入Event Table并注冊函數;
當指定的事情完成是,Event Table會將這個函數移入Event Queue;
主線程的任務執行完畢為空是,會去Event Queue讀取對應的函數,進入主線程執行;
上述過程會不斷重復,也就是常熟的Event Loop(事件循環)。
cmd-markdown-logo
注:該圖來源于網絡
9.2.2 宏任務和微任務角度
從宏任務和微任務角度來看,整個事件循環機制可分為以下步驟:
獲取一個宏任務開始執行,在執行時先創建一個微任務隊列,當遇到微任務的時候將微任務放到微任務隊列;
接著獲取微任務隊列中的所有微任務,然后依次執行(注:在執行微任務的時候產生的微任務將放到微任務的隊列的尾部,在本次循環中執行完畢)
循環執行該過程。
9.3 實戰
事件循環的核心內容就是需要區分清楚任務類型,只要將任務類型劃分好之后按照順序依次執行即可,下面用一段代碼演示以下整個的輸出內容。
- console.log('start');
- setTimeout(function() {
- console.log('setTimeout1');
- const promise2 = new Promise((resolve, reject) => {
- console.log('promise2');
- resolve();
- });
- promise2.then(() => {
- console.log('then2');
- const promise3 = new Promise(resolve => {
- console.log('promise3');
- resolve();
- });
- promise3.then(() => {
- console.log('then3');
- });
- });
- }, 1000);
- setTimeout(function() {
- console.log('setTimeout2');
- const promise4 = new Promise((resolve, reject) => {
- console.log('promise4');
- resolve();
- });
- promise4.then(() => {
- console.log('then4');
- });
- }, 1000);
- const promise1 = new Promise(resolve => {
- console.log('promise1');
- resolve();
- });
- promise1.then(() => {
- console.log('then1');
- });
- console.log('end');
只要了解整個事件循環機制,很容易可以得出答案,答案如下所示:
- start
- promise1
- end
- then1
- setTimeout1
- promise2
- then2
- promise3
- then3
- setTimeout2
- promise4
- then4
本文轉載自微信公眾號「前端點線面」,可以通過以下二維碼關注。轉載本文請聯系前端點線面公眾號。