到目前為止,我們一直在宣告變數、寫函式,但有沒有發現:同樣一個變數名稱,在不同地方可能會「互不影響」?
這就是作用域:變數在哪裡可用、存活多久。
簡單說,作用域就是變數可以被存取的範圍。
在 JS 裡主要有兩種常見範圍:
全域:整支程式都能讀到。
區塊/函式作用域:只在某個 {} 裡面或函式裡面有效。
let x = 10; // 全域範圍
function test() {
let y = 5; // 區塊/函式範圍
console.log(x); // 可以讀到全域的 x
console.log(y); // 5
}
test();
console.log(x); // 10
console.log(y); // ❌ 會報錯,y 不在全域範圍
var
只有函式作用域,沒有區塊作用域,所以在if
或for
內宣告的var
會跑出來
跑出去let
/const
則有,比較安全
如果程式在某個範圍找不到變數,就會往外層找,一層一層往外找,這叫做作用域鏈
let a = 1;
function outer() {
let b = 2;
function inner() {
let c = 3;
console.log(a, b, c); // 1 2 3
}
inner();
}
outer();
inner
找不到a
會往外找,找不到b
也會往外找,所以它可以讀到外層的東西。
閉包聽起來很神秘,其實就是「一個函式記住它出生時的外部環境,即使外部函式已經執行完畢。」
範例:
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
}
}
const counter1 = makeCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter1()); // 3
makeCounter
執行後本來應該結束,count
變數理論上要消失,但因為回傳的函式「閉包」記住了外層的 count
,所以count
還能繼續用,而且每個makeCounter
都有自己的count
。
為什麼閉包很重要?可以做「私有變數」,不暴露在全域;或是可以保存狀態,像計數器、暫存資料,很多 JS API(事件監聽、setTimeout 等)都是靠閉包工作的
小練習
寫一個函式,它回傳另一個函式,每次呼叫都會印出 Hello, name!
function createGreeter(name){
return function(){
console.log(`Hello, ${name}!`);
}
}
const greetAlice = createGreeter('Alice');
greetAlice(); // Hello, Alice!
greetAlice(); // Hello, Alice!
這就是閉包:greetAlice 記住了 name。
總結一下:作用域決定了變數在哪裡能被存取,let/const
有區塊作用域,var
只有函式作用域。
閉包就是函式記住外部變數的能力,閉包常用來做私有變數、保存狀態