初學者指南:深入了解 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
可以訪問 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。