# 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) ``` ![](https://i.imgur.com/ZWWUqce.jpg) ## 執行環境的運行 ### <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 的答案 檢查 : ![](https://i.imgur.com/7q53vrI.png =250x) #### <font color="purple">將 var 換成 let 得到不同結果,與作用域有關 ★★★★★</font> ![](https://i.imgur.com/n6WZga9.png =300x) > 這邊可以重新複習一下作用域的內容,[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 %}