Logo

新人日誌

首頁關於我部落格

新人日誌

Logo

網站會不定期發佈技術筆記、職場心得相關的內容,歡迎關注本站!

網站
首頁關於我部落格
部落格
分類系列文

© 新人日誌. All rights reserved. 2020-present.

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

最後更新:2024年10月19日JavaScript

在學習 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 可以訪問 innerVar、outerVar 和 globalVar。這是因為 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。


參考資料

  • MDN Web Docs – Lexical Scope
  • JavaScript.info – Lexical Environment
目前還沒有留言,成為第一個留言的人吧!

發表留言

留言將在審核後顯示。

JavaScript

目錄

  • 什麼是 Lexical Scope?
  • 定義
  • 為什麼 Lexical Scope 重要?
  • Lexical Scope 是如何運作的?
  • 作用域的層級
  • 作用域鏈
  • Lexical Scope 的範例
  • 內部函數可以訪問外部變數
  • 外部函數無法訪問內部變數
  • 綜合練習
  • 解析範例
  • Lexical Scope 的應用:閉包(Closures)
  • 閉包的概念
  • 閉包範例
  • 如何利用 Lexical Scope 寫出更好的代碼
  • 避免全域變數污染
  • 保護私有變數
  • 結語
  • 參考資料