先說結論。作用域及作用域查找鍊,前者是指宣告變數的使用範圍(邊界)涵蓋哪裡。後者是指在程式碼區塊內,使用一個不在同區塊內宣告的變數,因為在當前範圍找不到值 會由內往外找的機制。
變數的生命週期則是執行程式離開了宣告變數的作用域(區塊)後,變數會消失。
使用var、let/const宣告變數後的使用範圍。
當程式執行離開了該宣告變數的作用域的邊界後,在該作用域內宣告的變數會被系統回收 (Garbage Collected),實際上就是「消失」了。這種現象在程式設計中稱為 「變數生命週期結束 (Variable Lifetime Ends)」。
這也是為什麼用let 和 const 宣告變數比用 var 宣告變數更有優勢。let/const只要程式執行離開 {} 區塊,變數就可以被立即標記回收。這減少了記憶體的佔用。而用var宣告變數,變數要等到整個函式執行結束,才能被標記回收,記憶體的佔用時間就變得更長。
使用var宣告某個變數 這個變數能使用(能存取到的值) 會在離它最近的 function {} 大括號。它忽略 if 或 for 的 {}。
函式作用域 (Function Scope)
javascript
function testVar() {
// 變數 y 只能在 testVar 函式內部存取
if (true) {
var y = 10;
console.log("A. 內部:", y); // 輸出 A. 內部: 10
}
// 雖然離開了 if 的 {} 區塊,但因為是 var,它仍在函式作用域內
console.log("B. 外部:", y); // 輸出 B. 外部: 10
}
testVar();
console.log("C. 全域:", y); ❌ 報錯:函式外部無法存取函式內部的 var
console.log("A. 內部:", y); 會輸出 >> A. 內部: 10,這部分應該沒問題,因為這個var宣告的y就在function testVar的{}裡面。
console.log("B. 外部:", y); 會輸出 >> B. 外部: 10,這部分也能理解,因為這個var宣告範圍是在function testVar的{}裡面,而這行也是在函式testVar的{}裡面,一樣能找到這個y。
最後一行 console.log("C. 全域:", y); 因為在當前作用域找不到變數y,因為宣告var這個變數y是在函式裡面才能使用,所以會出現會找不到而出現錯誤 >> ReferenceError: y is not defined。
邊界是 離它最近的 {} 大括號。包含函式、if、for、甚至單純的 {}。
區塊作用域 (Block Scope)
javascript
// 👈 第 3 層:全域作用域 開始
function testLet() {
// 👈 第 2 層:函式作用域 開始
let x = 5;
if (true) {
// 區塊作用域 開始 (if 區塊)
let x = 10;
console.log("C. 內部:", x); // 輸出 10
}
// 區塊作用域 結束 (內層 x 銷毀)
console.log("D. 外部:", x); // 輸出 5
}
// 函式作用域 結束 (x = 5 在這裡被銷毀!)
testLet();
console.log("E. 全域:", x); // ❌ ReferenceError! (x 已在上面的 } 結束時被銷毀)
console.log("C. 內部:", x); 會輸出 >> C. 內部: 10,這部分應該沒問題,因為這個let宣告的x就在if的{}裡面。
console.log("D. 外部:", x); 會輸出 >> D. 外部: 5,這部分應該也可理解,因為在同區塊有使用let宣告 x = 5。
console.log("E. 全域:", x); 會輸出 >> ReferenceError: x is not defined。因為此區塊已經最外層,也就是所謂的全域作用域,但在此層並沒有找到x,所以會出錯。
javascript
function testConst() {
const PI = 3.14; // 這裡的 PI
if (true) {
const PI = 3.14159; // 內層 PI (新變數,遮蔽)
console.log("F. 內部 PI:", PI); // 輸出 F. 內部 PI: 3.14159
// PI = 10; // ❌ 報錯:TypeError (不可對 const 重新賦值)
}
console.log("G. 外部 PI:", PI); // 輸出 G. 外部 PI: 3.14 (外層 PI 的值未變)
}
testConst();
console.log("F. 內部 PI:", PI); // 會輸出 >> F. 內部 PI: 3.14159,這部分應該沒問題。
console.log("G. 外部 PI:", PI); // 會輸出 >> G. 外部 PI: 3.14 (外層 PI 的值未變)。
javascript
const A = 100; // 👈 第 3 層:全域變數
function outerFn() {
const B = 50; // 👈 第 2 層:外部函式變數
function innerFn() {
const C = 20; // 👈 第 1 層:內部函式變數
// 1. 嘗試查找 A (未在 innerFn 宣告)
// 查找鏈啟動:內層 -> 外層 -> 全域
console.log("H. A:", A); // 輸出 H. A: 100 (找到全域的 A)
// 2. 嘗試查找 B (未在 innerFn 宣告)
// 查找鏈啟動:內層 -> 外層
console.log("I. B:", B); // 輸出 I. B: 50 (找到 outerFn 的 B)
// 3. 嘗試查找 C (已在 innerFn 宣告)
console.log("J. C:", C); // 輸出 J. C: 20 (直接使用內部的 C)
// 4. 嘗試查找 X (沒有在任何一層宣告)
// console.log("K. X:", X); // ❌ 報錯:ReferenceError (查找鏈到底,找不到)
}
innerFn();
}
outerFn();
console.log("H. A:", A); // 輸出 H. A: 100。可以看出在區塊(function innerFn)裡沒有A,所以往外層找,沒有找到就再往外層找,找到A為100。
console.log("I. B:", B); // 輸出 I. B: 50。所在區塊(function innerFn)裡沒有B,所以往外層找,找到B為50。
console.log("J. C:", C); // 輸出 J. C: 20。所在區塊(function innerFn)裡有C=20。
console.log("K. X:", X); // 輸出 ReferenceError :X is not defined。