當 JavaScript 載入全域執行環境(Global Execution Context)
也許呼叫了某個函式建立新的執行環境(Function Execution Context)
等函式執行完回到全域環境後結束,一切都是「同步」進行的。
這讓 JavaScript 在「單執行緒」的情況下,仍能同時處理多個事件。
例如一個部落格網站,網頁一打開就必須:
若採用同步方式來拉資料,整個網站在資料回來之前都會「卡住」。
這就是非同步機制存在的意義:讓畫面能繼續互動,同時背景抓資料。
瀏覽器除了 JavaScript 引擎外,還有許多其他模組在協同運作:
JavaScript 可能會在執行過程中請求這些模組的幫忙
注意:
這些「非同步工作」並不在 JavaScript 引擎裡面執行,它們由瀏覽器的其他部分負責。
在 JavaScript 引擎看來,它仍是「同步」在跑,只是外部有機制幫它記錄該做的事。
這兩個東西不屬於 JavaScript 本身,而是瀏覽器的一部分。
執行環境堆疊(Execution Stack)
當函式被呼叫時,會建立一個新的執行環境;
若在此函式內又呼叫其他函式,會在上方再疊一層。
上層未結束前,下層無法執行(包含全域環境)。
如果某個堆疊執行太久(例如死循環),整個主程式就會被「卡死」。
有些函式(例如 setTimeout、setInterval)其實不是 JavaScript 內建的,而是瀏覽器提供的 Web API。
常見的 Web API :
document.getElementById
XMLHttpRequest
setTimeout
、setInterval
這些 API 的工作通常比較耗時,因此瀏覽器會先把它們交給 Web API 模組處理,
而不是讓主執行緒「乾等」。等到處理完成後,才會將結果丟進 Event Queue。
以 setTimeout 為例:
function executeAfterDelay() {
console.log("一秒之後才會執行");
}
setTimeout(executeAfterDelay, 1000);
console.log("我會先被執行");
執行步驟如下:
setTimeout
時,交給瀏覽器計時(不會阻塞主程式)。console.log("我會先被執行")
)。function executeAfterDelay() {
console.log("應該要馬上執行");
}
setTimeout(executeAfterDelay, 0);
console.log("應該要最後執行");
即使設定 0ms,仍會先執行主程式,因為 Event Queue 的任務會等堆疊清空後才跑。
Event Table 是一個用來記錄「哪些事件完成後要執行什麼函式」的資料集合。
當你執行 setTimeout() 時
JavaScript 會把:
一起放進 Event Table。
等時間到(非同步目的達成),再把該函式正式推進 Event Queue,等待主線程空閒後執行。
這個流程讓 JavaScript不會因等待資料或時間而卡住整個程式。