初學者指南:深入了解 JavaScript 的 Lexical Scope(詞法作用域)

更新日期: 2024 年 10 月 19 日

在學習 JavaScript 的過程中,Lexical Scope(詞法作用域)是你會經常遇到的一個重要概念。

它決定了程式中的變數和函數,在哪些位置可以被訪問和使用。

理解詞法作用域對於掌握 JavaScript 中的變數範圍和作用域鏈至關重要。

本文將為新手解釋什麼是 Lexical Scope,並通過簡單的範例幫助你掌握這一概念。


什麼是 Lexical Scope?

定義

Lexical Scope,也稱為靜態作用域,是 JavaScript 中的一種作用域規則,它在編寫程式碼時決定了變數和函數的可訪問範圍。

具體來說,JavaScript 的作用域,是根據程式的詞法結構(即代碼的嵌套關係)來確定的。

這意味著在函數定義時,決定了哪些變數和函數可以被訪問,而與函數何時或在哪裡被調用無關。

為什麼 Lexical Scope 重要?

理解 Lexical Scope 有助於新手掌握以下關鍵知識:

  • 變數的可訪問範圍:你能在程式的哪些部分訪問某個變數或函數。
  • 作用域鏈的運作原理:當變數在當前作用域中找不到時,JavaScript 如何在外部作用域中查找。

Lexical Scope 是如何運作的?

在 JavaScript 中,Lexical Scope 的範圍,取決於變數和函數的嵌套結構

當你在一個函數中聲明變數時,該變數只能在這個函數的作用域內(以及嵌套的內部作用域中)訪問,而無法在外部作用域中訪問。

作用域的層級

JavaScript 的作用域可以分為兩種類型:

  • 全域作用域(Global Scope):在程式的最外層定義的變數和函數,處於全域作用域中,並且可以被整個程式訪問。
  • 函數作用域(Function Scope):在函數內部定義的變數和函數,只能在該函數內部訪問,這是 Lexical Scope 的核心。

作用域鏈

當一個變數在當前作用域內部找不到時,JavaScript 會沿著作用域鏈向上查找,直到全域作用域。這個查找過程形成了所謂的作用域鏈

範例:

let globalVar = "I am global";

function outerFunction() {
  let outerVar = "I am outer";

  function innerFunction() {
    let innerVar = "I am inner";
    console.log(innerVar);    // 可以訪問 innerVar
    console.log(outerVar);    // 可以訪問 outerVar
    console.log(globalVar);   // 可以訪問 globalVar
  }

  innerFunction();
}

outerFunction();

在這個範例中,innerFunction 可以訪問 innerVarouterVarglobalVar。這是因為 innerFunction 可以通過作用域鏈,依次訪問內部和外部的變數。


Lexical Scope 的範例

內部函數可以訪問外部變數

在 JavaScript 中,內部函數可以訪問外部函數的變數,這是 Lexical Scope 的一個核心特性。

範例:

function outer() {
  let outerVar = "外部變數";

  function inner() {
    console.log(outerVar);  // 可以訪問外部變數
  }

  inner();
}

outer(); // 輸出:外部變數

在這個例子中,inner 函數可以訪問 outerVar,即使這個變數是在外部函數 outer 中定義的。

這就是 Lexical Scope 的運作方式。

外部函數無法訪問內部變數

Lexical Scope 是單向的。這意味著,雖然內部函數可以訪問外部函數的變數,但外部函數無法訪問內部函數的變數。

範例:

function outer() {
  let outerVar = "外部變數";

  function inner() {
    let innerVar = "內部變數";
    console.log(innerVar); // 可以訪問內部變數
  }

  inner();
  console.log(innerVar); // 會拋出錯誤,因為無法訪問內部變數
}

outer();

在這裡,當你嘗試在 outer 函數中訪問 innerVar 時,會拋出錯誤,因為 innerVar 只在 inner 函數的作用域內有效。

綜合練習

let x = 1;

function test() {
  let x = 2;
  hey();
}

function hey() {
  console.log(x);
}

test();

解析範例

  • 在這個範例中,我們首先在全域作用域中聲明了一個變數 x,並將它設置為 1
  • 然後,我們定義了一個函數 test,在該函數內部聲明了一個局部變數 x,並將它的值設置為 2。這個 x 只存在於 test 函數內部。
  • test 函數中,我們調用了 hey 函數,而 hey 函數並沒有定義自己的 x 變數。

當我們執行 test() 時,hey 函數被調用。

根據 Lexical Scope 的規則,hey 函數會查找最近的變數 x

由於 hey 函數在定義時是在全域作用域中,它會沿著作用域鏈查找全域作用域中的 x,因此會輸出 1,而不會查找到 test 函數中的局部變數 x(值為 2)。

輸出:

1

這展示了 Lexical Scope 的運作方式:函數會基於它定義時的作用域來決定變數的訪問範圍,而不是根據它被調用的位置。


Lexical Scope 的應用:閉包(Closures)

閉包的概念

閉包是 JavaScript 中的一個強大特性,並且是 Lexical Scope 的直接應用。

當內部函數「記住」了它的外部函數的變數,即使外部函數已經執行完畢,這個內部函數仍然可以訪問這些變數。這就是閉包。

閉包範例

function outer() {
  let outerVar = "外部變數";

  return function inner() {
    console.log(outerVar);  // 可以訪問外部變數,即使外部函數已經執行完畢
  };
}

const closureFunction = outer();
closureFunction(); // 輸出:外部變數

在這個例子中,inner 函數是一個閉包,它可以在 outer 函數執行完畢後,依然訪問 outerVar

這是因為 inner 函數「閉包」了它在定義時的作用域,從而保存了對 outerVar 的訪問權限。


如何利用 Lexical Scope 寫出更好的代碼

避免全域變數污染

理解 Lexical Scope 可以幫助你避免過度使用全域變數,這會減少變數命名衝突的風險。你可以將變數限制在特定函數內,讓代碼更加模塊化和安全。

保護私有變數

利用 Lexical Scope,你可以創建私有變數,這些變數只能在特定的作用域內被訪問,這對於保護敏感數據或防止變數被意外修改非常有用。

範例:

function createCounter() {
  let count = 0;

  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 輸出:1
console.log(counter()); // 輸出:2

在這個範例中,count 是一個私有變數,只能通過返回的閉包函數來操作。

這使得 count 變數被保護在它的作用域內,無法被外部直接修改。


結語

Lexical Scope 是 JavaScript 中一個至關重要的概念,它決定了變數和函數的可訪問範圍。

理解這一概念可以幫助新手撰寫更加結構化、模塊化且易於維護的程式碼。

通過 Lexical Scope,你可以掌握如何在不同的作用域中管理變數,並利用閉包來保護私有變數。

希望本文能夠幫助你更好地理解和應用 JavaScript 中的 Lexical Scope。


參考資料

Similar Posts