# JavaScript - Event-Loop


這次在練習一些前端常見的面試提時，遇到了一個有關 Event Loop 的題目問題，實在不是很暸解確切的執行過程，因此記錄一下學習筆記。

# JavaScript - Event-Loop

關於 Event-Loop 在瀏覽器執行時的優點有兩項：

1. Event Loop 協助了非同步請求的實現，並產生連貫的畫面呈現（influent UI）。
   ![同步請求(Synchronous) v.s. 非同步請求(Asynchronous) (from: Alpha Camp)](https://miro.medium.com/max/1400/0*yO-MJbI-q0SmERE9.png "同步請求(Synchronous) v.s. 非同步請求(Asynchronous) (from: Alpha Camp")

2. Event Loop 將「費時較久」或「須等待事件才能啟動」的任務往後安排，透過事件往後安排，能夠將網頁效能進行適當的配置，進而打造流暢以及較高的使用者體驗。

## JavaScript 同步執行的缺點，搭配 Event Loop 進行解決

如果使用原生 JavaScript 那你應該經常會碰到資料渲染上的問題，當需要 render 的區塊，資料來源是使用第三方 API，在瀏覽器等待回應（Response）時，畫面會停在奇怪的顯示階段，預覽圖是有做 Loading 圖示，如果沒有做的話，使用者體驗更是糟糕。
![瀏覽器等待 request 回應中](https://miro.medium.com/max/1400/0*zKoRxXjOdi0fZpzL.gif "瀏覽器等待 request 回應中")

因此 Event Loop 就在這樣的情境下因應而生，可以說它是依附著瀏覽器而存在的一個事件監聽器，用以控管各項任務順暢執行。
當瀏覽器現階段沒有同步的 JS 代碼需要執行時，才會將排隊中的額外事件安排進去執行。

## Event Loop 特性

這使用了下方三個形式，都會觸發 Event Loop 的效果：

- Call stack
  一個後進先出（ LIFO = Last In, First Out）的執行堆疊（call stack）。會依序執行函式：從全域（Global Scope）的主程式（main program）開始，逐一把各個函式推（push）至執行堆疊的最上方，並從最後進入的函式開始執行。當函式結束後（reurn），會將此函式抽離（pop off）堆疊。

- Web APIs
  瀏覽器提供了很多不同的 API（例如：DOM、AJAX、Timeout），讓我們能夠同時（concurrently）處理多項任務。當完成 Web APIs 的內部函式後（如 setTimeout()），便將任務傳遞至工作佇列。

- Callback Queue
  這是一個先進先出（ FIFO = First In, First Out）的工作佇列（callback queue） 。會接收從 Web APIs 來的任務，並透過 Event Loop 的監控，當堆疊中沒有執行項目時，便把佇列中的內容拉進堆疊中執行。

### Event Loop (easy) 範例：

```javaScript
const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  bar()
  baz()
}

foo()

/* foo
** bar
** baz
*/
```

透過以下流程圖，我們可以明白完整的執行順序：
![A simple event loop explanation (from: Flavio Copes)](https://miro.medium.com/max/1400/0*42kZatm9hw4u_BTu.png "A simple event loop explanation (from: Flavio Copes)")

### Event Loop 進階版

接著我們花點時間思考一下，上面這支程式如何執行。應該可以得出以下結果：

```javaScript
console.log('Hi')

setTimeout(function cb1() {
    console.log('cb1')
}, 5000)

console.log('Bye')

/* Hi
** Bye
** cb1
*/
```

透過以下 GIF 動畫，可以幫助我們理解執行流程的全貌：

![Code execution in browser (from: SessionStack)](https://miro.medium.com/max/1400/0*0EpvJV7Wc6TLNtF6.gif "Code execution in browser (from: SessionStack)")

### Event Loop 搭配上 for Loop 迴圈 進階題

```javaScript
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

/* 3
** 3
** 3
*/
```

這裡的答案是會 console 出三次的 3，明明 for 迴圈後方有限制了，無法讓 `i` 變數大於 3 ，但答案卻還是回傳了 3 ，為什麼呢？

執行詳細過程：

1. for 迴圈結束時，變數的改變過程為 0,1,2 ，很合理的 `i=2` 變數沒有大於 3
2. 透過前面的範例以及敘述，我們這裡會知道瀏覽器會將 `setTimeout` 放入了 queue 裡頭，最後才去執行
3. 由於 var 的特性是屬於全域的狀況，會將 `i=2` 變數重新注入此函示去進行 console ，加上 for 迴圈特性，第一次進入的變數，不管 for 迴圈後方的限制，都會去執行第一遍，因此會在執行一次 `i++` ，所以最後的答案會是 `i=3`

---

假如上述那題目改為 `let` 來進行宣告 `i` 變數呢？答案會是什麼呢？

```javaScript
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

/* 0
** 1
** 2
*/
```

:::tip

`let` 是區塊作用域，因此雖然 `setTimeout` 一樣會觸發 Event Loop，但變數不會重新回到全域當中，因此 console 出來的答案會是 console 出：`0,1,2`

:::

