了解 JavaScript 和瀏覽器的運作原理,會更容易理解什麼是同步與非同步。
我原本對同步與非同步完全不了解,但自從看了[筆記] 理解 JavaScript 中的事件循環、堆疊、佇列和併發模式@PJCHENder
那些沒告訴你的小細節這篇文章後,獲得很好的解釋,以下是我對這篇文章的理解。
@PJCHENder 的文章中有影片註解,可幫助理解非同步的流程!
中/英文:
執行環境 (runtime)
執行堆疊(called stack)
堆疊(stack)
抽離(pop off)
阻塞(blocking)
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
如果執行的是一個無窮迴圈時,就會一直進入瀏覽器有容量上限,超過就會 overflow
。
雖然 JavaScript 的 Runtime
一次做一件事,但瀏覽器提供了更多不同的 API 可以使用,配合 event loop 同時處理多個事項。
瀏覽器 runtime 是用:引擎 V8
當然 Node.js 也有他的 Runtime
,這邊主要說明瀏覽器的 Runtime
。
瀏覽器有提供很多 webAPI(第三方做的各種東西可以直接取用)
ex: console.log
, setTimeout
, callback function
stack
,運行 event loop
監聽工具,event loop
開始運行是當整個程式碼跑完, stack
裡面沒有需要執行時,會開始運行 event loop
,看 task queue
裡面有沒有東西,如果有則依序抓出來放到 stack
中執行。
task queue
處理 webapi
處理回來的 !!!
下方 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)')
setTimeout 的延遲時間,是在 WebAPI 中倒數,並非呈現出來的延遲時間。
也就是即使延遲時間為 0 ,也是在 WebAPI 延遲 0 秒,執行完畢回傳內容到 task queue
等待 stack
為空,啟動 event loop
,執行 swetTimeout
中的內容。
第二個雖然比較晚進去 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 呼叫
一秒後,四個 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)
JavaScript:
function p1(){
for (var i=0; i<3; i++){
setTimeout(()=>{
console.log(i)
}, i*1000)
}
}
p1();
// 3
這邊可以拆分幾個段落,可以看成程式碼執行到第 9 行時,stack
會出現:
p1();
function p1();
,function 中有 for 迴圈
setTimeout(()=>{
console.log(i)
}, i*1000)
}
檢查 :
複習:
回呼函式: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
JS