闭包(Closure) 是指一个 函数能够访问其外部作用域(函数外部)的变量,即使在外部函数执行结束后,变量仍然可以被访问。
闭包的原理
在 JavaScript 中,每当创建一个函数时,就会同时创建一个闭包。闭包使得内部函数可以“记住”其创建时的词法作用域(Lexical Scope) ,即使外部函数已经执行完毕并返回,内部函数依然可以访问外部函数的变量。
示例:经典闭包
function outerFunction() {
let count = 0; // 外部变量
return function innerFunction() {
count++; // 访问外部作用域的变量
console.log(count);
};
}
const counter = outerFunction(); // 返回一个内部函数
counter(); // 输出:1
counter(); // 输出:2
counter(); // 输出:3
分析
outerFunction()执行后返回innerFunction(),但count变量不会被销毁,因为innerFunction仍然持有对count的引用。counter()每次执行,都会增加count的值并打印出来,即使outerFunction已经执行完毕。- 这种行为就是闭包:函数记住并访问了创建它的作用域(
count变量) 。
实际应用场景
1. 数据私有化
闭包可以用于创建私有变量,避免变量污染全局作用域:
function createCounter() {
let count = 0; // 私有变量
return {
increment: function () {
count++;
console.log(count);
},
decrement: function () {
count--;
console.log(count);
},
getCount: function () {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // 输出:1
counter.increment(); // 输出:2
console.log(counter.getCount()); // 输出:2
console.log(counter.count); // undefined,外部无法直接访问
优点:
count变量被封装在createCounter()内部,外部无法直接修改它,只能通过increment、decrement操作。
2. 延迟执行(定时器)
function delayedMessage(message, delay) {
setTimeout(() => {
console.log(message);
}, delay);
}
delayedMessage("Hello, world!", 2000); // 2秒后输出 "Hello, world!"
为什么是闭包?
setTimeout的回调函数是在delayedMessage执行完毕后才运行的,但它仍然能访问message变量。- 在适当的时候清除定时器clearInterval防止内存泄漏
3. 事件监听器
function attachEventListeners() {
let count = 0; // 闭包变量
document.getElementById("btn").addEventListener("click", function () {
count++;
console.log(`按钮点击次数: ${count}`);
});
}
attachEventListeners();
为什么是闭包?
click事件的回调函数仍然可以访问count变量,即使attachEventListeners已经执行完毕。- 防止内存泄露,在不再需要时,移除事件监听器removeEventListener
4. 柯里化(Currying)
function multiply(a) {
return function (b) {
return a * b;
};
}
const double = multiply(2);
console.log(double(5)); // 输出:10
console.log(double(10)); // 输出:20
为什么是闭包?
multiply(2)返回的函数仍然可以访问a = 2,即使multiply已经执行完毕。
总结
闭包的特点
- 内部函数可以访问外部函数的变量,即使外部函数已经执行结束。
- 变量不会被垃圾回收,因为它们仍然被内部函数引用。
- 适用于数据私有化、定时器、事件监听、柯里化等场景。
闭包既是 JavaScript 强大的特性,也是导致内存泄漏的潜在风险,因此要合理使用闭包,避免不必要的变量占用内存。