JavaScript的事件迴圈機制淺析

Lewyon發表於2022-03-16

前言

JavaScript是一門單執行緒的弱型別語言,但是我們在開發中,經常會遇到一些需要非同步或者等待的處理操作。
類似ajax,亦或者ES6中新增的promise操作用於處理一些回撥函式等。

概念

在JavaScript程式碼執行過程中,可以分為同步佇列和非同步佇列。

  1. 同步任務類似我們常說的立即執行函式,不需要等待可以直接進行,可以直接進入到主執行緒中去執行,類似正常的函式呼叫等。

  2. 非同步佇列則是非同步執行函式,類似ajax請求,我們在發起的過程中,會進入到一個非同步佇列,載入到任務當中時,需要進行等待,之後才能進行返回值的處理。

舉個例子

下面一段程式碼,我們可以先了解一些一些關於事件迴圈機制的一些基本的原理


console.log('1');
setTimeout(function() {
  console.log('4');
}, 0);
Promise.resolve().then(function() {
  console.log('2');
}).then(function() {
  console.log('3');
});
console.log('5');

我們將程式碼列印到控制檯當中,輸出結果是:1,5,2,3,4

我們知道,在JavaScript中,類似定時器,以及ES6新增的promise是非同步函式,回到我們上面所說的佇列的概念當中,不難得出,1和5為同步執行佇列

在執行完同步佇列中的程式碼之後,再執行非同步佇列中的程式碼。

TIP

在解析非同步佇列的promise和定時器中,我們發現,定時器setTimeout是後執行於promise,這裡我們引入JavaScript規範中的巨集任務(Macro Task)和微任務(Micro Task)的概念

在JavaScript中,巨集任務包含了:script( 整體程式碼)、setTimeout、setInterval、I/O、UI 互動事件、setImmediate(Node.js 環境)

微任務:Promise、MutaionObserver、process.nextTick(Node.js 環境)

再回到上面的定時器和promise的問題,這時候我們知道,JavaScript中,當有非同步佇列的時候,優先執行微任務,再執行巨集任務

再次舉個例子

假如在非同步佇列當中存在非同步佇列時,我們需要怎麼處理

console.log(1);
setTimeout(function() {
  console.log(5);
}, 10);
new Promise(resolve => {
    console.log(2);
    resolve();
    setTimeout(() => console.log(3), 10);
}).then(function() {
    console.log(4);
})
console.log(6);

將程式碼執行到控制檯中,得出的列印順序是:1,2,6,4,5,3

  • 不同於例子1當中的promise,列印2是優先於6執行的,由此我們可以知道,new Promise在執行過程中,在未執行resolve或者rejected前,所執行的程式碼均為同步佇列中的程式碼。

  • 再看4,5,3的執行順序,在執行微任務promise執行回撥resolve之後,對應的then立即執行

  • 在列印結果中,定時器5優先執行於---->屬於微任務promise中的巨集任務定時器3,定時器5這個巨集任務是在promise微任務這個佇列之後就加進去,在promise執行完成then回撥之後,promise中的巨集任務才加入到佇列當中,因此在定時器5之後執行

總結

在JavaScript中,巨集任務包含了:script( 整體程式碼)、setTimeout、setInterval、I/O、UI 互動事件、setImmediate(Node.js 環境)

微任務:Promise、MutaionObserver、process.nextTick(Node.js 環境);

在執行過程中,同步程式碼優先於其他任務佇列中的程式碼,
定時器,promise這類任務,在執行過程中,會先加入佇列,
在執行完同步程式碼之後,再根據巨集任務和微任務的分類,先執行微任務佇列,再執行巨集任務佇列。

文章個人部落格地址:JavaScript的事件迴圈機制

相關文章