閉包概念
閉包(Closure)是 JavaScript 中的一個核心概念,它指的是函式和其外部環境(或詞法作用域)之間的組合。簡單來說,閉包是指當一個函式在其外部作用域中引用了變數時,該函式和這些變數的組合形成了一個閉包。
閉包的表現
閉包通常是由一個外部函式包裹一個內部函式,且返回內部函式,從而使得內部函式可以訪問外部函式作用域中的變數。但這並不是閉包的唯一形式。
閉包的本質是 函式“記住”了它所在的詞法作用域,即使這個函式是在其定義的作用域之外執行的。
常見的閉包
function outerFunction() {
let count = 0;
return function innerFunction() {
count++;
console.log(count);
};
}
const closure = outerFunction();
closure(); // 輸出 1
closure(); // 輸出 2
不返回函式的閉包
即使外部函式不返回內部函式,閉包仍然存在,只要內部函式訪問了外部函式中的變數。例如,在事件監聽器或定時器中。
function setupClickListener() {
let count = 0;
document.getElementById('myButton').addEventListener('click', function() {
count++;
console.log(count); // 訪問了外部函式的變數 count
});
}
setupClickListener();
雖然 setupClickListener 沒有返回內部函式,但內部函式仍然形成了閉包,因為它引用了外部函式中的 count 變數。
定時器中的閉包
function startTimer() {
let count = 0;
setInterval(function() {
count++;
console.log(count); // 訪問外部函式的 count
}, 1000);
}
startTimer();
使用場景
資料封裝
閉包允許建立私有變數,這些變數在函式外部無法直接訪問。透過閉包,可以建立具有私有狀態的物件,從而實現資料封裝。例如:
function createCounter() {
let count = 0; // count 是私有變數
return function () {
count++;
return count;
};
}
const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
const counter2 = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
閉包可以用來建立模組化的程式碼結構,使得模組內的變數和函式不會汙染全域性名稱空間。例如:
const Module = (function () {
let privateVar = "I am private";
function privateMethod() {
console.log(privateVar);
}
return {
publicMethod: function () {
privateMethod();
}
};
})();
Module.publicMethod(); // 輸出 "I am private"
閉包的關鍵在於它能夠“記住”外部函式的狀態,即使外部函式已經執行完畢。這個特性使得閉包在 JavaScript 中非常有用,尤其是在涉及到資料隱私、狀態管理和函式工廠等方面。
延遲執行
閉包可以用來延遲執行某些程式碼,尤其在處理非同步操作時。例如:
function delayedGreeting(name) {
return function () {
console.log(`Hello, ${name}!`);
};
}
const greetJohn = delayedGreeting("John");
setTimeout(greetJohn, 1000); // 1秒後輸出 "Hello, John!"
事件處理
閉包常用於事件處理程式,以便訪問事件處理程式內的資料。例如:
function setupButton(buttonId) {
const button = document.getElementById(buttonId);
let count = 0;
button.addEventListener("click", function () {
count++;
console.log(`Button clicked ${count} times`);
});
}
setupButton("myButton");
記憶體洩漏
閉包可以導致記憶體洩漏,因為閉包會保留對其外部作用域的引用。如果閉包引用的變數或函式沒有被及時釋放,垃圾回收機制可能不會回收這些記憶體,從而導致記憶體洩漏。
例如,在事件監聽器中使用閉包,如果不及時移除事件監聽器,可能會導致記憶體無法被釋放:
function createListener() {
let element = document.getElementById('myElement');
element.addEventListener('click', function() {
console.log('Clicked!');
});
}
createListener();
這個例子中,如果不手動移除事件監聽器,即使元素被刪除了,閉包仍然會保留對它的引用,導致記憶體洩漏。
避免過度依賴閉包來儲存狀態
可以透過將狀態和邏輯分離來減少閉包的使用。例如,使用物件、類或者資料結構來管理狀態,而不是全部放入閉包中。
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
return this.count;
}
}
const counter = new Counter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2