JS中的事件循環機制

javascript是一門單線程、非阻塞的語言。任何時候,都只有一個主線程來處理所有的任務。

當遇到異步事件時,並不會一直等待異步代碼返回結果,而是會將這個事件掛起,繼續執行執行棧中的其他任務。當異步事件返回結果後,js會將異步事件添加到另一個與主執行棧不同的隊列,稱之爲異步任務隊列。

被添加到異步任務隊列中的回調並不會立即執行,而是會等主執行棧的任務執行完畢後,再去檢查任務隊列(這裏的任務隊列,又分爲微任務和宏任務)裏是否有任務。首先會去讀取微任務隊列(micro task)是否有事件存在,如果不存在,接着會讀取宏任務隊列(macro task),把宏任務隊列中的事件回調,推入到主執行棧進行執行。如果存在,會依次讀取微任務隊列,直到所有的微任務都執行完畢,再去讀取宏任務隊列的事件,把其對應的回調代碼推入到主執行棧中進行執行,如此反覆執行,形成循環----也就是我們所說的事件循環。

爲了更明確事件循環的流程,可通過 下圖 或 僞代碼 幫助記憶:
在這裏插入圖片描述
什麼是事件循環(記憶僞代碼):

// eventLoop是一個用作隊列的數組 //(先進,先出)
var eventLoop = [ ];
var event;
//“永遠”執行 
while (true) {
	// 一次tick
	if (eventLoop.length > 0) {
		// 拿到隊列中的下一個事件 
		event = eventLoop.shift();
		// 現在,執行下一個事件 
		try {
			event(); 
		} catch (err) {
			reportError(err);
		} 
	}
}

宏任務(macro task) 主要包含:script、setTimeout、setInterval、I/O、UI 交互事件
微任務(micro task) 主要包含:Promise、MutaionObserver

接下來再來看我們經常遇到的面試考題:

console.log('script start');

setTimeout(function() {
  console.log('timeout1');
}, 10);

new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})

console.log('script end');

以上代碼執行流程爲:

  • 會先執行同步代碼:執行console.log(‘script start’);
  • 接着遇到異步事件setTimeout,會添加到宏任務隊列等待執行。
  • new Promise會立即執行,然後執行內部的console.log(‘promise1’);
  • 接着又遇到new Promise內部的異步事件setTimeout,仍然添加到宏任務隊列等待執行;
  • new Promise內的代碼執行完之後,會遇到promise.then異步事件,它爲微任務事件,所以添加到微任務等待執行;
  • 接着又會執行下面的同步代碼console.log(‘script end’);
  • 同步代碼執行完畢,會先讀微任務事件,這時微任務中含有promise.then(), 推入主執行棧執行,會打印 ‘then1’;
  • 微任務執行完畢,去讀取宏任務中的事件,依次執行後打印 ‘timeout1’ 和 ‘timeout2’;

所以以上代碼打印結果爲:script start、promise1、script end、then1、timeout1、timeout2

注: 一定要清楚,setTimeout(…) 並沒有把你的回調函數掛在事件循環隊列中。它所做的是設定一個定時器。當定時器到時後,環境會把你的回調函數放在事件循環中,這樣,在未來某個時刻的 tick 會摘下並執行這個回調。

如果這時候事件循環中已經有 20 個項目了會怎樣呢? 你的回調就會等待。它得排在 其他項目後面——通常沒有搶佔式的方式支持直接將其排到隊首。這也解釋了爲什麼 setTimeout(…) 定時器的精度可能不高。大體說來,只能確保你的回調函數不會在指定的 時間間隔之前運行,但可能會在那個時刻運行,也可能在那之後運行,要根據事件隊列的 狀態而定。

?小結:微任務優先於宏任務執行,所以如果有需要優先執行的異步邏輯,可放入 micro task 隊列會比 macro task 更早的被執行。

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