iT邦幫忙

2022 iThome 鐵人賽

DAY 4
1

前言

在這篇文章中,將會介紹 JavaScript 的 Runtime Environment,讓讀者更了解 JS 的執行過程。


JavaScript Runtime Environment

說到 JS 的 Runtime Environment,那就肯定是要搬出這張圖去做解釋!

左邊的區塊就是 JS 引擎,在引擎內部會有 Call Stack,但是單靠引擎內部運作的話,程式一次只會做一件事,一個任務執行完後才會執行其他任務,也就是同步(Synchronous)。

但瀏覽器在執行任務時是多執行緒的,因此可以執行好幾個任務,常見的執行緒包括:

  1. JavaScript 引擎執行緒
    負責解析與執行 JS。
  2. GUI 執行緒
    渲染瀏覽器的畫面,包括建立 DOM Tree。
  3. 定時觸發器執行緒
    經典代表為 setIntervalsetTimeout
  4. 非同步 http 請求執行緒
    處理非同步的請求,請求完成後發送通知給事件觸發執行緒。
  5. 事件觸發執行緒
    例如滑鼠點擊事件,或是從定時觸發器執行緒、事件觸發執行緒等內部任務完成所觸發的事件。

其中,那些瀏覽器執行緒裡面的事件,像是 setTimeout,XMLHttpRequest等就屬於非同步事件,你可能需要等待一小段時間才能得到 response 的任務。

假如非同步事件會和同步事件都一起放進 Call stack,那麼就會造成阻塞,比如我們發出一個 HTTP 的請求,那麼在完成回傳 response 之前,就會因為 JS 單執行緒的特性,我們在網頁上都無法進行其它動作。

而在 JavaScript Runtime Environment,透過 Event Loop、Task Queue 能讓 JS 順利的執行非同步事件。在 JavaScript Runtime Environment 內部,主要分成了五個部分:

  1. JS Engine
  2. Web API
  3. Task Queue/Callback Queue
  4. Job Queue/Microtask Queue
  5. Event loop

之前已經有介紹過 JS 引擎了,所以就跳過,以下說明另外四個部分:

1. Web API

瀏覽器提供的 API,包括:

  1. DOM API: document.getElementById, addEventListerner
  2. AJAX
  3. Timer 函式: setTimeout、setInterval

2. Task Queue(也可以稱 Callback Queue)

它是一個佇列的資料結構,當 Web API 的非同步事件觸發時,其 callback function 會進入到 Task Queue 裡面,等 Call stack 裡面沒有其它任務才會進入到 Call stack 執行。

主要存放 Macrotasks,像是 setTimeout、setImmediate、UI 互動事件。

Macrotasks 歸類於瀏覽器提供

3. Job Queue(也可以稱 Microtask Queue)

存放 Microtasks,例如 Promise .then/catch/finally 函式、MutationObserver、process.nextTick。

Microtasks 的函式偏向是 V8 引擎提供的 API

Microtasks 會先執行,完成後才執行 Task Queue 內的 Macrotasks

4. Event loop

透過 Event loop,可以判斷是否要將非同步任務加到 Call Stack 執行,其監測步驟如下:

  1. 看看 Call Stack 裡面有沒有任務,有的話執行,沒有就往第 2 步驟
  2. 看看 Job Queue 裡面有沒有 Microtask,有的話就把任務移到 Call stack 執行(跳到第 4 步驟),沒有的話跳第 3 步驟
  3. 看看 Task Queue 裡面有沒有 Macrotask,有的話搬一個 Macrotask 到 Call stack 執行
  4. 執行完任務,DOM 結構有更新則重新渲染畫面,再回到第 1 步驟

執行範例

在以下的網站中,將整個 Runtime Environment 視覺化並附上了一段程式碼,有興趣的讀者可以進去網站並執行程式碼。

latentflip.com/loupe


題目練習

以下兩道題目讀者可以練習看看,如果有不清楚或是回答錯誤的話,可以將前面的內容再多看一下喔~

練習題 1

以下有五個 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

練習題 2

這段程式碼會印出什麼?

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!


上一篇
Day3-JavaScript 記憶體管理
下一篇
Day5-JavaScript Execution Context & Hoisting & Scope Chain
系列文
強化 JavaScript 之 - 程式語感是可以磨練成就的30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

1
json_liang
iT邦研究生 5 級 ‧ 2022-09-04 10:36:22

感謝大大解說,讓我能夠理解非同步概念

harry xie iT邦研究生 1 級 ‧ 2022-09-04 10:50:35 檢舉

謝謝支持!

2
雷N
iT邦研究生 1 級 ‧ 2022-09-04 13:06:40

這些概念與NodeJS的處理非同步的機制很像耶!
講的淺顯易懂 讚

我要留言

立即登入留言