Try   HackMD

JavaScript 和瀏覽器的運作原理

了解 JavaScript 和瀏覽器的運作原理,會更容易理解什麼是同步與非同步。

我原本對同步與非同步完全不了解,但自從看了[筆記] 理解 JavaScript 中的事件循環、堆疊、佇列和併發模式@PJCHENder
那些沒告訴你的小細節
這篇文章後,獲得很好的解釋,以下是我對這篇文章的理解。

@PJCHENder 的文章中有影片註解,可幫助理解非同步的流程!

中/英文: 執行環境 (runtime) 執行堆疊(called stack) 堆疊(stack) 抽離(pop off) 阻塞(blocking)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

執行環境的運行

堆疊(stack)

堆疊(stack):遵守『後進先出』規則

stack 堆疊,顧名思義就是從最下方一直往上堆起來,像盤子堆起來一樣,拿起盤子也要遵守從最上方拿起,也就是最後放上去的那一個!

執行堆疊(called stack)

程式碼會依序在 stack 中被執行,
當執行環境裡面的任務都執行完畢,就會被移除換下一個。

假設,程式碼執行到某一函式,此時此函式會出現在 stack 中的最上方,如果執行完畢就會被抽離(pop off)。

範例:

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

當然 Node.js 也有他的 Runtime,這邊主要說明瀏覽器的 Runtime

更詳細可以參考 在不同 runtime 上執行的 JavaScript @rock070

webAPI

瀏覽器有提供很多 webAPI(第三方做的各種東西可以直接取用)

ex: console.log , setTimeout, callback function

事件監聽 (event loop)

執行時機:空的 stack ,運行 event loop

監聽工具,event loop 開始運行是當整個程式碼跑完, stack 裡面沒有需要執行時,會開始運行 event loop ,看 task queue 裡面有沒有東西,如果有則依序抓出來放到 stack 中執行。

工作佇列 (task queue):先進先出

task queue 處理 webapi 處理回來的 !!!

以 setTimeout 為例,解釋非同步

下方 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)')

setimeout = 0 常見面試考題!★★★☆☆

setTimeout 的延遲時間,是在 WebAPI 中倒數,並非呈現出來的延遲時間。
也就是即使延遲時間為 0 ,也是在 WebAPI 延遲 0 秒,執行完畢回傳內容到 task queue 等待 stack 為空,啟動 event loop ,執行 swetTimeout 中的內容。

setimeout 時間長先傳入 v.s. 時間短後傳入★★★★☆

第二個雖然比較晚進去 webapi ,但他比較快被算好,就會先出現

注意:setimeout 計算時間是在 WebAPI 中運行的,並不是代表幾秒後是出現時間

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 呼叫

Multiple setTimeout 面試考題 ★★★★★

一秒後,四個 hi 依序出現,瀏覽器打開檢查看起來比較像是同時出現!

這是因為程式碼片段依序將 console.log('hi'),到 stack 中,然後又被移到 WebAPI 去執行 1 秒,執行完後,到 task queue 中,此時 stack 中是空的,所以會運行 event loop ,然後把剛剛到達 task queue 中的 console.log('hi') 放到 stack 中,所以看起來比較像是同時出現!

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)

進階問題: for 回圈 + setTimeout ★★★★★

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 未知
      ​​​​​​​​setTimeout(()=>{ ​​​​​​​​ console.log(i) ​​​​​​​​ }, i*1000) ​​​​​​​​}
    • i=2 符合 i<3 ,WebAPI 執行 setTimeout,此時 i 未知
    • i=3 不符合 i<3
  4. 雖然 for 迴圈中會出現i = 0,1,2 ,三個值,但並沒有被接收到,而 i = 3 不符合 i<3 遺留下來,這個遺留下來的值被 setTimeout 所接收,i =3 帶入 WebAPI 的 setTimeout 中
  5. WebAPI 有三個 setTimeout,皆會帶入 i=3
  6. 所以得到三個 3 的答案

檢查 :

將 var 換成 let 得到不同結果,與作用域有關 ★★★★★

這邊可以重新複習一下作用域的內容,for + varfor + let

非同步回呼 callback function

複習:

回呼函式:function 帶入一個參數 ,那個參數是一個 function

工作佇列(task queue):
callback function 依序排隊一個一個返回 stack,誰先進來誰先出去

範例JavaScript:

function text(callback){ console.log('run text') callback(); } function cb(){ console.log('run me later') } test(cb); // run text // run me later

參考:
全域執行環境與全域物件 @pvt5r486

tags: JS

最後,親愛的大家!我需要你的大聲鼓勵 ٩(⚙ᴗ⚙)۶

如果覺得這篇文章對你有幫助,請給我個一個小小的鼓勵 ❤ 讓我知道,這會成為我寫下去很大的動力。
對了,我還有其他文章,如果有興趣也來逛逛吧!
(文章中如有覺得不妥之處、錯誤內容,也可以透過聯絡我,我會儘速改善,感謝!)

☞ YoJanni 珍妮 2021 正在設計轉職前端的路上,希望大家在學習的路上能夠一起成長
☞ 聯絡我