在這篇文章中,將會介紹 JavaScript 的 Runtime Environment,讓讀者更了解 JS 的執行過程。
說到 JS 的 Runtime Environment,那就肯定是要搬出這張圖去做解釋!
左邊的區塊就是 JS 引擎,在引擎內部會有 Call Stack,但是單靠引擎內部運作的話,程式一次只會做一件事,一個任務執行完後才會執行其他任務,也就是同步(Synchronous)。
但瀏覽器,以 Chrome 為例,是**多進程(multi processes) & 多執行緒(multi threads)**的,因此可以同時執行好幾個任務,詳細 Chrome 內部有哪些可參考底下兩個連結:
Inside look at modern web browser
其中,那些瀏覽器執行緒裡面處理的事件/任務,像是 setTimeout,XMLHttpRequest等就屬於非同步事件,你可能需要等待一小段時間才能得到 response 的任務。
假如非同步事件會和同步事件都一起放進 Call stack,那麼就會造成阻塞,比如我們發出一個 HTTP 的請求,那麼在完成回傳 response 之前,就會因為 JS 單執行緒的特性,我們在網頁上都無法進行其它動作。
而在 JavaScript Runtime Environment,透過 Event Loop、Task Queue 能讓 JS 順利的執行非同步事件。在 JavaScript Runtime Environment 內部,主要分成了五個部分:
之前已經有介紹過 JS 引擎了,所以就跳過,以下說明另外四個部分:
瀏覽器提供的 API,包括:
它是一個佇列的資料結構,當 Web API 的非同步事件觸發時,其 callback function 會進入到 Task Queue 裡面,等 Call stack 裡面沒有其它任務才會進入到 Call stack 執行。
主要存放 Macrotasks,像是 setTimeout、setImmediate、UI 互動事件。
Macrotasks 歸類於瀏覽器提供
存放 Microtasks,例如 Promise .then/catch/finally
函式、MutationObserver、process.nextTick。
Microtasks 的函式偏向是 V8 引擎提供的 API
透過 Event loop,可以判斷是否要將非同步任務加到 Call Stack 執行,其監測步驟如下:
在以下的網站中,將整個 Runtime Environment 視覺化並附上了一段程式碼,有興趣的讀者可以進去網站並執行程式碼。
以下兩道題目讀者可以練習看看,如果有不清楚或是回答錯誤的話,可以將前面的內容再多看一下喔~
以下有五個 console.log,試著判斷它們印出的先後順序:
setTimeout(() => {
console.log('I will be printed out first.');
}, 0);
const promiseExample = new Promise(function (resolve) {
resolve('But I still faster than you, Mr.SetTimeout. I am a microtask.')
}).then((data) => {
console.log(data);
})
function mockFunc() {
console.log('Shut up! I am a synchronous task so I will run first.');
}
mockFunc();
也許從對話就能看出結果了? XD
這段程式碼會印出什麼?
console.log("A");
setTimeout(function () {
console.log("B");
}, 0);
while(true) {} // 死循環
答案是只會印出 A,由於要等到 Call stack 的同步任務都完成,非同步的任務才會透過 event loop 進到 Call stack 執行,但 while loop 一直不會執行完,所以就不會印出 B。
Day5 [JavaScript 基礎] Event Loop 機制
Demystifying Asynchronous JavaScript — Event Loop, Call stack, Task Queue, and More!