1. 程式人生 > 實用技巧 >簡述Js的事件迴圈

簡述Js的事件迴圈

普通訊息佇列

一個程式碼塊中的所有同步程式碼,都會被看作一個巨集任務,新增到普通訊息佇列的尾部

延遲執行佇列

當你在程式碼中使用 setTimeout 或 setInterval 時,會建立一個巨集任務
延遲指定的時間後,將其放入延遲執行佇列的尾部

事件迴圈系統

為了簡化敘述,我們可以理解為:每一輪事件迴圈都執行一個巨集任務,每一輪事件迴圈都關聯一個微任務佇列
具體過程如下:

  • 檢查普通訊息佇列是否為空,若不為空,則從佇列頭部取出一個巨集任務執行;否則,從延遲執行佇列頭部取出一個巨集任務執行
  • 執行過程中如果遇到 setTimeout 或 setInterval,延遲指定時間後,將其作為一個巨集任務 新增到延遲執行佇列的尾部
  • 執行過程中如果建立了微任務(Promise 或者 MutationObserver),就將它新增到當前的微任務佇列中
  • 執行當前微任務佇列中的所有微任務
  • 開始下一個迴圈

例題1:

console.log('--開始--');

setTimeout(() => {
  console.log('timer1');  
}, 0);

new Promise((resolve, reject) => {
  for (let i = 0; i < 5; i++) {
    console.log(i);
  }
  resolve()
}).then(()=>{
  console.log('Promise'); 
})

console.log('--結束--');

//---開始--
//0
//1
//2
//3
//4
//--結束--
//Promise
//timer1

第一輪迴圈:普通訊息佇列中只有一個巨集任務(整個script)

  • 遇到console.log,直接輸出 "--開始--"
  • 遇到setTimeout,因為指定時間為0ms,所以立即將其回撥函式timer1新增到延遲執行佇列的尾部
  • 遇到Promise的executor,直接執行,依次輸出0、1、2、3、4,遇到resolve,將其回撥函式新增到當前的微任務佇列中
  • 遇到console.log,直接輸出 “--結束--”
  • 檢查微任務佇列,發現一個微任務,立即執行,輸出“Promise”

第二輪迴圈:普通訊息佇列為空,延遲執行佇列中有一個巨集任務(timer1)

  • 執行timer1的回撥函式
  • 發現微任務佇列為空,本輪迴圈結束

例題2:

new Promise((resolve) => {
  console.log('this is executor');
  resolve();
}).then(() => {
  console.log('this is resolve');
  setTimeout(() => { // 命名為timer1
    console.log('timer1');
  }, 4)
})

setTimeout(() => { // 命名為timer2
  console.log('timer2');
}, 4)

console.log('-- 結束--');

//this is executor
//-- 結束--
//this is resolve
//timer2
//timer1

第一輪迴圈:普通訊息佇列中只有一個巨集任務(整個script)

  • 遇到Promise的executor,直接執行,輸出“this is executor”
  • 遇到resolve,將其回撥函式新增到當前的微任務佇列中
  • 遇到setTimeout,在4ms後將timer2新增到延遲執行佇列的尾部
  • 遇到console.log,直接輸出 “--結束--”
  • 檢查微任務佇列,發現一個微任務,立即執行
  • 遇到console.log,直接輸出 “this is resolve”
  • 遇到setTimeout,在4ms後將timer1新增到延遲執行佇列的尾部,本輪迴圈結束

第二輪迴圈:普通訊息佇列為空,延遲執行佇列頭部為timer2

  • 執行timer2,輸出timer2

第三輪迴圈:延遲執行佇列頭部為timer1

  • 執行timer1,輸出timer1