2024-05-10
#JavaScriptJs Event Loop 筆記
#基本概念
首先要先知道幾個概念:
- 棧 (Stack):
- 一種資料結構,特性為後進先出(LIFO)。
- 當函式執行時,會被放到 stack 最上面,執行完成後移除,接著往下執行下個函式,直到 stack 清空。
- 列隊 (Queue):
- 一種資料結構,特性為先進先出(FIFO)。
- 待處理的任務會被放到 Queue 裡面,當 Stack 清空時,才會去執行 Queue 裡的任務。
- Queue 裡會儲存待處理的非同步操作,例如:定時器、事件處理程序回調等。所以在事件循環中,也會稱它為 Callback Queue 或是 Task Queue。
- 事件循環 (Event Loop): 不斷檢查 Stack 是否為空,若為空就把 Queue 裡的任務放到 Stack 中執行,如此循環往復。
- Web API:Web API 是瀏覽器提供的方法,例如:
setTimeout
、XMLHttpRequest
...等,運作於瀏覽器端,並不是 JavaScript 引擎的一部分,所以他們可以同時執行。
#事件循環步驟
- Step1. 執行 Call Stack
由上而下執行 Call Stack 裡的內容
- Step2. 檢查 Call Stack
檢查 Stack 是否為空,如果它為空,表示目前沒有待處理的同步程式碼。
如果遇到異步任務,執行環境會調用相關的 API (例如在瀏覽器上會調用 Web API),等待此異步任務的結果之後,再被放置到 Task Queue 中。
- Step3. 讀取 Task Queue
事件循環接下來會讀取 Task Queue,並將任務隊列第一個,加到執行棧中運行。
- 循環此流程
當 Call Stack 為空,就讀取 Task Queue,不斷重複這個步驟,直到所有任務完成。
#宏任務 (Macro Task) 與微任務 (Micro Task)
- JavaScript 中的異步任務又分成宏任務和微任務。
- 宏任務一次只執行 "一個"
- 如果有微任務,就依序執行完 "所有" 微任務
常見的宏任務與微任務
- 宏任務:
Script
(整體程式碼)、setTimeout
、setInterval
、UI渲染、事件 - 微任務:
Promise
的then
、catch
、finally
方法、MutationObserver
、await
後的程式碼
執行順序如下:
- 執行宏任務(最一開始會是整個
Srcipt
) - 若遇到宏任務,就放到 Task Queue 中的宏任務列隊;若遇到微任務,就放到 Task Queue 中的微任務列隊
- 當 call stack 空了,檢查微任務列隊,執行完所有的微任務
- 再回到第一步,執行宏任務
#練習
題目ㄧ
下面這段程式碼的 console 結果為何?
console.log(123);
setTimeout(() => {
console.log('Hello');
}, 1000);
console.log('World');
解答
123;
('World');
('Hello');
說明
- 依順序執行程式碼,先印出
123
- 往下到 setTimeout,把 setTimeout 放到 Stack
- 把 setTimeout 從 Stack 取出,交由 Web API 處理
- 再往下執行,印出
World
- 過了 1000 毫秒,把 setTimeout 裡面的 callback function 放到 Queue 裡。
- 此時 Stack 裡已沒東西,讀取 Queue,印出
Hello
題目二
下面這段程式碼的 console
結果為何?
console.log('begins');
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => {
console.log('promise 1');
});
}, 0);
new Promise(function (resolve, reject) {
console.log('promise 2');
setTimeout(function () {
console.log('setTimeout 2');
resolve('resolve 1');
}, 0);
}).then((res) => {
console.log('dot then 1');
setTimeout(() => {
console.log(res);
}, 0);
});
解答
'begins';
'promise 2';
'setTimeout 1';
'promise 1';
'setTimeout 2';
'dot then 1';
'resolve 1';
說明
- 依順序執行程式碼,因此先印出
begins
- 接著跑到 setTimeout,把它放到 Task Queue 的宏任務
- 再往下走,執行 new Promise 裡的內容,會印出
promise 2
- 又遇到 setTimeout,把它放到 Task Queue 的宏任務
- 此時主線程 (Stack) 空了,去看 Task Queue 的任務,Task Queue 有兩個 setTimeout (兩個都是宏任務)
- 先執行第一個 setTimeout,因此會印出
setTimeout 1
,往下遇到 Promise.resolve,把它放到 Task Queue 的微任務 - 由於宏任務一次只執行一個,因此不會再執行第二個 setTimeout,而是去看有沒有微任務。
- 發現 Task Queue 裡此時有第 6 步放進的 Promise.resolve,所以印出
promise 1
- 再回來執行宏任務 setTimeout,印出
setTimeout 2
- 呼叫 resolve 所以進入到 .then 於是印出
dot then 1
- 又有一個 setTimeout,把它放到 Task Queue。此時 Stack 是空的,Task Queue 也沒有微任務了,因此執行 setTimeout,印出
resolve 1
。
參考資料