# JavaScript 和瀏覽器的運作原理
了解 JavaScript 和瀏覽器的運作原理,會更容易理解什麼是同步與非同步。
我原本對同步與非同步完全不了解,但自從看了[[筆記] 理解 JavaScript 中的事件循環、堆疊、佇列和併發模式@PJCHENder
那些沒告訴你的小細節](https://pjchender.blogspot.com/2017/08/javascript-learn-event-loop-stack-queue.html)這篇文章後,獲得很好的解釋,以下是我對這篇文章的理解。
@PJCHENder 的文章中有影片註解,可幫助理解非同步的流程!
```css=
中/英文:
執行環境 (runtime)
執行堆疊(called stack)
堆疊(stack)
抽離(pop off)
阻塞(blocking)
```

## 執行環境的運行
### <span class="bg-b">堆疊(stack)</span>
#### 堆疊(stack):遵守『<font color="red">後進先出</font>』規則
`stack` 堆疊,顧名思義就是從最下方一直往上堆起來,像盤子堆起來一樣,拿起盤子也要遵守從最上方拿起,也就是最後放上去的那一個!
#### 執行堆疊(called stack)
程式碼會依序在 `stack` 中被執行,
當執行環境裡面的任務都執行完畢,就會被移除換下一個。
假設,程式碼執行到某一函式,此時此函式會出現在 `stack` 中的最上方,如果執行完畢就會被抽離(`pop off`)。
範例:
```javascript=
let a ;
function start(){
a = 3;
b();
}
function b(){
console.log('Hello')
}
start();
// 1. 程式碼從第 1 行開始一直到第 9 行依序執行,呼叫 start()
// 2. function start() 進入 stack
// 執行 function start(), a = 3 且呼叫 b()
// 此時, function start() 執行完畢並 pop out
// 3. function b() 執行
// 印出 console.log('Hello'),pop out
```
### stack overflow 無窮迴圈
如果執行的是一個無窮迴圈時,就會一直進入瀏覽器有容量上限,超過就會 `overflow` 。
## 非同步處理與堆疊
### 執行環境 runtime
雖然 JavaScript 的 `Runtime` 一次做一件事,但瀏覽器提供了更多不同的 API 可以使用,配合 event loop 同時處理多個事項。
瀏覽器 runtime 是用:[引擎 V8](https://zh.wikipedia.org/wiki/V8_(JavaScript%E5%BC%95%E6%93%8E))
當然 Node.js 也有他的 `Runtime`,這邊主要說明瀏覽器的 `Runtime`。
> 更詳細可以參考 [在不同 runtime 上執行的 JavaScript @rock070](https://ithelp.ithome.com.tw/articles/10242676)
### <span class="bg-b">webAPI</span>
瀏覽器有提供很多 [webAPI](https://developer.mozilla.org/en-US/docs/Web/API)(第三方做的各種東西可以直接取用)
ex: `console.log` , `setTimeout`, `callback function`
### <span class="bg-b">事件監聽 (event loop)</span>
#### 執行時機:空的 `stack` ,運行 `event loop`
監聽工具,`event loop` 開始運行是當整個程式碼跑完, `stack` 裡面沒有需要執行時,會開始運行 `event loop` ,看 `task queue` 裡面有沒有東西,如果有則依序抓出來放到 `stack` 中執行。
### <span class="bg-b">工作佇列 (task queue)</span>:先進先出
`task queue` 處理 `webapi` 處理回來的 !!!
#### 以 setTimeout 為例,解釋非同步
下方 JavaScript 為例:
```javascript=
console.log('im(console.log1)')
// 1. 執行 console.log('im(console.log1)')
setTimeout(function () {
console.log('im(console.log3)')
}, 5000)
// 2. 執行 setTimeout
// 3. 發現是 WebAPI ,會移動到 WebAPI 運行,接著繼續執行下段程式碼
console.log('im(console.log2)')
// 4. 執行 console.log('im(console.log2)')
// 5. 此時, 程式碼執行到最後一行, stack 中也是空的,便開始運行 event loop
// 6. event loop 監聽 task queue 中已經運行完成的 WebAPI (setTimeout)
// 7. 發現 setTimeout 執行完成回傳console.log('im(console.log3)')
// 8. stack 執行 console.log('im(console.log3)')
```
#### <font color="purple">setimeout = 0 常見面試考題!★★★☆☆</font>
setTimeout 的延遲時間,是在 WebAPI 中倒數,並非呈現出來的延遲時間。
也就是即使延遲時間為 0 ,也是在 WebAPI 延遲 0 秒,執行完畢回傳內容到 `task queue` 等待 `stack` 為空,啟動 `event loop` ,執行 `swetTimeout` 中的內容。
#### <font color="purple">setimeout 時間長先傳入 v.s. 時間短後傳入★★★★☆</font>
第二個雖然比較晚進去 webapi ,但他比較快被算好,就會先出現
:::warning
注意:setimeout 計算時間是在 WebAPI 中運行的,並不是代表幾秒後是出現時間
:::
```javascript=
setTimeout(function timeout(){
console.log('hi-1')
}, 1000)
setTimeout(function timeout(){
console.log('hi-2')
}, 5000)
// 1 秒後
// hi-1
// 5 秒後
// hi-2
// 注意:1 秒後、5 秒後是在 WebAPI 中運行的,並不是代表幾秒後是出現時間
setTimeout(function timeout(){
console.log('hi-1')
}, 5000)
setTimeout(function timeout(){
console.log('hi-2')
}, 1000)
// hi-2
// hi-1
// WebAPI 會先進 hi-1 計算五秒,接著 hi-2 計算 1 秒,但因為 hi-2 計算時間比較短,會較快進入 task queue 中等待被 event loop 呼叫
```
#### <font color="purple">Multiple setTimeout 面試考題 ★★★★★</font>
一秒後,四個 hi 依序出現,瀏覽器打開檢查看起來比較像是同時出現!
這是因為程式碼片段依序將 `console.log('hi')`,到 `stack` 中,然後又被移到 `WebAPI` 去執行 1 秒,執行完後,到 `task queue` 中,此時 `stack` 中是空的,所以會運行 `event loop` ,然後把剛剛到達 `task queue` 中的 `console.log('hi')` 放到 `stack` 中,所以看起來比較像是同時出現!
JavaScript :
```javascript=
setTimeout(function timeout() {
console.log('hi')
}, 1000)
setTimeout(function timeout() {
console.log('hi')
}, 1000)
setTimeout(function timeout() {
console.log('hi')
}, 1000)
setTimeout(function timeout() {
console.log('hi')
}, 1000)
```
#### <font color="purple">進階問題: for 回圈 + setTimeout ★★★★★</font>
JavaScript:
```javascript=
function p1(){
for (var i=0; i<3; i++){
setTimeout(()=>{
console.log(i)
}, i*1000)
}
}
p1();
// 3
```
這邊可以拆分幾個段落,可以看成程式碼執行到第 9 行時,`stack` 會出現:
1. 呼叫 `p1();`
2. 執行 `function p1();`,function 中有 for 迴圈
3. 執行 for 迴圈,
- i=0 符合 i<3,WebAPI 執行 setTimeout,此時 i 未知
```javascript=
setTimeout(()=>{
console.log(i)
}, i*1000)
}
```
- i=2 符合 i<3 ,WebAPI 執行 setTimeout,此時 i 未知
- i=3 不符合 i<3
5. 雖然 for 迴圈中會出現i = 0,1,2 ,三個值,但並沒有被接收到,而 i = 3 不符合 i<3 遺留下來,這個遺留下來的值被 setTimeout 所接收,i =3 帶入 WebAPI 的 setTimeout 中
6. WebAPI 有三個 setTimeout,皆會帶入 i=3
7. 所以得到三個 3 的答案
檢查 :

#### <font color="purple">將 var 換成 let 得到不同結果,與作用域有關 ★★★★★</font>

> 這邊可以重新複習一下作用域的內容,[for + var](https://hackmd.io/@unayojanni/HyzWz9R0u/%2Fm5NKLfVcROu_iC7DCMDX0w#✐-var-與-for-迴圈)和 [for + let](https://hackmd.io/@unayojanni/HyzWz9R0u/%2FS4h4XxLRStavTPFJ3RQFjg#let-會存在-block-裡)
### 非同步回呼 callback function
複習:
> 回呼函式:function 帶入一個參數 ,那個參數是一個 function
> 工作佇列(task queue):
`callback function` 依序排隊一個一個返回 `stack`,誰先進來誰先出去
範例JavaScript:
```javascript=
function text(callback){
console.log('run text')
callback();
}
function cb(){
console.log('run me later')
}
test(cb);
// run text
// run me later
```
> 參考:
> [全域執行環境與全域物件 @pvt5r486](https://medium.com/pvt5r486/javascript-weird-day-2-%E5%85%A8%E5%9F%9F%E5%9F%B7%E8%A1%8C%E7%92%B0%E5%A2%83%E8%88%87%E5%85%A8%E5%9F%9F%E7%89%A9%E4%BB%B6-428bc0d61085)
###### tags: `JS`
{%hackmd @unayojanni/H1Qq0uKkK %}