就如前幾篇所提到的,由於JavaScript是單線程的程式語言,因此它無法同時處理多個任務,這時候就需要一個機制來處理異步代碼,從而避免阻塞主線程並保持程序的響應性。也就是所謂的異步編成。
而異步編成的工作程序大致如下:
1.同步代碼 會在主線程(即執行棧 Call Stack)上立即執行。
2.當JavaScript遇到異步操作(如定時器、I/O 請求、DOM事件等),會將這些操作交給瀏覽器的Web API或Node.js的內置 API 處理。
3.當這些異步操作完成後,它們的回調函數會被放入宏任務隊列(Task Queue)或微任務隊列(Microtask Queue)中,等待 Call Stack清空時再執行。
Event Loop的作用就是持續監控Call Stack和Task Queue。當Call Stack清空時,Event Loop會將消息隊列中的回調函數推入Call Stack,然後開始執行這些回調函數。要注意的是,一些特殊的異步任務會被放入微任務隊列當中,比如Promise的.then()回調或MutationObserver。微任務的優先級比消息隊列高,Event Loop會在每次檢查Task Queue之前,先執行Microtask Queue裡的任務。
同步代碼:會立即執行並占用 Call Stack。
異步代碼:如 setTimeout()、Promise 等,會交由 Web API或Node API處理,並在完成後將回調放入Task Queue或Microtask Queue中等待執行。
宏任務 (Macro-task):每次 Event Loop 迴圈都會處理一個宏任務。常見的宏任務包括:
1.setTimeout
2.setInterval
3.DOM事件
4.I/O任務(如HTTP請求)
微任務(Micro-task):微任務在每個宏任務結束後執行,優先級高於宏任務。常見的微任務包括:
1.Promise的.then()
2.MutationObserver
3.process.nextTick(Node.js)
微任務設計的初衷是讓需要快速反應的小任務能夠在同一輪事件循環中及時被執行,而不必等到下一次事件循環。這對於處理像Promise這類異步操作的回調特別重要,因為我們希望異步操作一旦完成,其回調能夠在不阻塞或延遲的情況下立即執行,保持應用的高效性和反應靈敏度。
console.log('Start');
setTimeout(() => {
console.log('Timeout callback');
}, 0);
Promise.resolve().then(() => {
console.log('Promise then callback');
});
console.log('End');
1.console.log('Start') 和 console.log('End') 這些同步代碼會立即執行。(輸出Start End)
2.Promise.then() 回調作為微任務,會在同步代碼執行完後立即執行,優先於setTimeout的回調(宏任務)。
(輸出Promise then callback)
3.setTimeout()的回調是宏任務,會在微任務執行完後才被執行。(輸出Timeout callback)
這表示了.then()回調會被優先處理,以確保異步Promise的結果能夠被快速反映到程序中,這是將其放入微任務隊列的主要原因。
1.如果一個 Promise被解決,我們會希望它的.then()回調函數能夠盡快執行,以確保代碼在不同條件下能夠保持一致的行為。
2.假設Promise的回調被放入Task Queue,那麼其他的宏任務(如 setTimeout 的回調)可能會先於.then() 回調執行,導致異步操作的結果出現不一致的行為。將.then( 回調放入Microtask Queue,則能保證當前任務完成後,緊接著會處理微任務,從而確保回調的順序保持一致。
MutationObserver是一個監聽DOM變化的 API,它也被設計成使用於微任務隊列。這是因為DOM變化通常是由同步操作引發的,如果我們將回調放入宏任務隊列,可能會延遲這些變化的處理。
通過將MutationObserver放入微任務隊列,JavaScript 可以在 DOM 變化發生後,盡快做出響應,從而確保DOM的更新和事件處理可以在同一輪事件循環內完成,提升了用戶界面的反應速度。