初學者指南:深入了解 JavaScript 的 Call Stack(呼叫堆疊)

更新日期: 2024 年 11 月 21 日

在學習 JavaScript 的過程中,你可能會聽到「Call Stack」(呼叫堆疊)這個術語。

Call Stack 是 JavaScript 執行程式時的核心機制之一,它負責追蹤函式的執行順序,並處理函式之間的呼叫關係。

理解 Call Stack 的運作原理對新手來說至關重要,因為它有助於你理解程式的執行流程,以及如何排查程式錯誤。

本文將深入介紹 JavaScript 中的 Call Stack 概念,並通過範例來幫助你更好地理解它的工作原理。


什麼是 Call Stack?

定義

Call Stack(呼叫堆疊)是 JavaScript 的一種資料結構,它用來追蹤程式中的函式執行。

每當一個函式被呼叫時,它會被添加到 Call Stack 的頂部,並且在函式執行完畢後,它會從 Call Stack 中移除。

堆疊結構

堆疊(Stack)是一種「後進先出」(LIFO, Last In First Out)的資料結構,這意味著最新進入堆疊的項目會最先被處理。

這與堆放盤子的概念類似:最新放上去的盤子需要先拿走,才能處理下面的盤子。


Call Stack 的運作原理

基本操作

Call Stack 主要負責處理函式的執行順序,每當一個函式被調用時,它會進入 Call Stack,而當函式執行完畢時,它會被移出 Call Stack。

運作步驟:

  1. 當程式執行時,主程式會首先被放入 Call Stack。
  2. 當一個函式被呼叫,它會被添加到 Call Stack 的頂部。
  3. 當這個函式執行完畢後,它會從 Call Stack 中移除,並返回到前一個函式。
  4. 這個過程會一直重複,直到 Call Stack 清空,程式執行結束。

Call Stack 的範例

讓我們通過一個簡單的範例來理解 Call Stack 的運作方式。

範例:基本的函式呼叫

function firstFunction() {
  console.log("First Function is called");
  secondFunction();
}

function secondFunction() {
  console.log("Second Function is called");
  thirdFunction();
}

function thirdFunction() {
  console.log("Third Function is called");
}

firstFunction();

執行步驟:

  1. 當程式開始執行時,firstFunction() 被呼叫,並添加到 Call Stack 中。
  2. firstFunction() 執行後,它呼叫了 secondFunction(),所以 secondFunction() 也被推入 Call Stack。
  3. 接著,secondFunction() 呼叫了 thirdFunction(),因此 thirdFunction() 也被推入 Call Stack。
  4. thirdFunction() 執行完畢,它會從 Call Stack 中被移除,回到 secondFunction()
  5. 然後 secondFunction() 完成,從 Call Stack 中移除。
  6. 最後,firstFunction() 執行完畢,也從 Call Stack 中移除。

這樣,Call Stack 就會清空,程式完成執行。

輸出:

First Function is called
Second Function is called
Third Function is called

Call Stack 的錯誤處理

堆疊溢位(Stack Overflow)

Call Stack 有一定的大小限制,當函式呼叫過於頻繁,導致 Call Stack 中的函式無法被正常移除時,就會出現堆疊溢位(Stack Overflow)錯誤。

這通常發生在遞迴呼叫過多次未停止的情況下。

堆疊溢位範例

function recursiveFunction() {
  recursiveFunction(); // 無限遞迴呼叫
}

recursiveFunction();

在這個例子中,recursiveFunction 一直呼叫自己,永遠不會停止,因此 Call Stack 不斷增長,最終導致堆疊溢位,並拋出錯誤:

Uncaught RangeError: Maximum call stack size exceeded

JavaScript 是單執行緒的

JavaScript 是一種單執行緒的語言,這意味著它一次只能做一件事。

Call Stack 是 JavaScript 執行程式的唯一途徑,當一個函式正在被執行時,其他函式必須等待,直到這個函式執行完畢,才會從 Call Stack 中移除,然後處理其他函式。

單執行緒與 Call Stack 的關係

由於 JavaScript 是單執行緒的,所以 Call Stack 是程式唯一的執行環境。

這意味著當某個函式長時間執行或進入無限循環時,它會阻塞其他函式的執行,導致整個程式掛起。

範例:阻塞程式執行

function longRunningFunction() {
  while (true) {
    // 無限循環,阻塞程式執行
  }
}

longRunningFunction();
console.log("這段代碼永遠不會執行");

由於 longRunningFunction 進入了無限循環,它一直佔據 Call Stack,導致其他代碼(如 console.log)無法執行。


異步行為與 Call Stack

雖然 JavaScript 是單執行緒的,但它可以通過事件循環來處理異步操作,如 setTimeoutPromise 等。

這些異步操作並不會阻塞 Call Stack,因為它們會被放入事件隊列,等待 Call Stack 清空後再進行處理。

範例:setTimeout 與 Call Stack

console.log("開始");

setTimeout(function() {
  console.log("這是一個延遲執行的訊息");
}, 2000);

console.log("結束");

輸出:

開始
結束
這是一個延遲執行的訊息

在這個例子中,setTimeout 的回調函式並不會立即執行,而是等到 Call Stack 清空後,通過事件循環再將回調函式放回 Call Stack 執行。因此,你會看到 "結束" 會在延遲訊息之前輸出。


Call Stack 的應用與排錯

理解錯誤堆疊

當 JavaScript 程式出現錯誤時,錯誤訊息中通常會包含錯誤堆疊追蹤(Stack Trace)。它可以告訴你哪個函式導致了錯誤,並展示函式的呼叫順序,幫助你排查錯誤。

範例:

function firstFunction() {
  secondFunction();
}

function secondFunction() {
  thirdFunction();
}

function thirdFunction() {
  throw new Error("這是一個錯誤!");
}

firstFunction();

這段程式碼會在 thirdFunction 中拋出錯誤,並顯示錯誤堆疊:

Uncaught Error: 這是一個錯誤!
    at thirdFunction (<anonymous>:8:9)
    at secondFunction (<anonymous>:4:9)
    at firstFunction (<anonymous>:1:9)
    at <anonymous>:1:1

這個錯誤堆疊告訴我們錯誤是如何從 thirdFunction 開始,並經由 secondFunctionfirstFunction 傳遞出來的,這對於排查錯誤非常有幫助。


面試常見問題與答案

理解 Call Stack 是 JavaScript 開發中的基礎,許多面試題目會涉及這一概念。以下是一些常見的面試問題以及相應的答案:

什麼是 Call Stack?它如何運作?

Call Stack 是 JavaScript 中的資料結構,負責追蹤和管理函式的呼叫和執行順序。

當一個函式被呼叫時,它會被添加到 Call Stack 的頂部,當函式執行完畢後,它會從 Call Stack 中移除。

Call Stack 是後進先出的(LIFO),後面進入的函式會優先被執行。

什麼是堆疊溢位(Stack Overflow)?如何避免?

堆疊溢位是指當 Call Stack 被填滿後,無法再添加新的函式,這通常發生在遞迴呼叫中,當函式無限遞迴時。

要避免堆疊溢位,應確保遞迴函式有適當的退出條件,防止函式無限遞迴。

JavaScript 是單執行緒語言,這對 Call Stack 有什麼影響?

由於 JavaScript 是單執行緒的,所有函式都在同一個 Call Stack 中執行。如果一個函式佔用 Call Stack 太久,其他函式將無法執行。

這意味著長時間運行的函式可能會阻塞程式的執行,造成應用程式卡頓。

因此,開發者需要謹慎處理長時間運行的任務,並考慮將它們轉移到異步操作中。

Call Stack 和 Event Loop 有什麼區別?

Call Stack 用來管理同步任務的執行順序,而 Event Loop 負責處理異步任務。

當 Call Stack 清空時,Event Loop 會從任務佇列中取出異步任務,並將它推入 Call Stack 進行執行。

這兩者共同作用,讓 JavaScript 能夠處理同步和異步任務。

如何在 JavaScript 中發生遞迴函式的情況下避免堆疊溢位?

在遞迴函式中,要確保有明確的結束條件,防止函式無限遞迴。

此外,可以使用尾遞迴優化(Tail Call Optimization)來減少堆疊的增長,但 JavaScript 中並非所有環境都支持這一技術。


結語

Call Stack 是 JavaScript 程式執行的基礎,它負責追蹤函式的呼叫順序,並確保每個函式在適當的時間被執行。

理解 Call Stack 可以幫助新手更好地掌握 JavaScript 的執行流程,並在遇到錯誤時能夠快速定位問題。

隨著對 Call Stack 概念的深入理解,你將能夠撰寫出更具結構性、性能優化的程式碼。

Similar Posts