闭包(Closure)是 JavaScript 中一个非常重要的概念,它允许函数访问并记住其词法作用域(lexical scope)中的变量,即使该函数在其作用域外执行。简单来说,闭包是一个函数 + 它能够访问的外部变量。
JavaScript 的作用域是词法作用域(静态作用域),即函数在定义时就确定了它能访问哪些变量,而不是在执行时确定。
function outer() {
const name = "Alice"; // name 是 outer 的局部变量
function inner() {
console.log(name); // inner 可以访问 outer 的变量 name
}
return inner;
}
const myFunc = outer();
myFunc(); // 输出 "Alice" —— 即使 outer 已经执行完毕,inner 仍然能访问 name
inner 函数在 outer 内部定义,因此它可以访问 outer 的变量 name。outer 执行完毕,inner 仍然能访问 name,这就是闭包。闭包可以模拟私有变量(类似 Java/C++ 的 private 变量)。
function createCounter() {
let count = 0; // 私有变量
return {
increment: () => count++,
decrement: () => count--,
getCount: () => count,
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
count 对外不可见,只能通过 increment、decrement、getCount 访问。闭包可以动态生成函数。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(3)); // 8 (5 + 3)
console.log(add10(3)); // 13 (10 + 3)
makeAdder 返回一个闭包,该闭包记住了 x 的值。闭包常用于事件监听和异步回调。
function setupButton() {
const button = document.getElementById("myButton");
let clickCount = 0;
button.addEventListener("click", () => {
clickCount++;
console.log(`Button clicked ${clickCount} times`);
});
}
setupButton();
() => { ... } 是一个闭包,可以访问 clickCount。闭包会保留对外部变量的引用,可能导致内存无法释放。
function heavyOperation() {
const bigData = new Array(1000000).fill("data"); // 大数据
return function() {
console.log(bigData.length); // 闭包引用 bigData
};
}
const fn = heavyOperation();
fn(); // 即使 heavyOperation 执行完毕,bigData 仍然被闭包引用,无法被垃圾回收
解决方案:在不需要时手动解除引用(如 fn = null)。
在 for 循环中使用闭包时,可能会遇到变量共享问题。
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 输出 5 次 5,而不是 0,1,2,3,4
}, 1000);
}
原因:var 是函数作用域,i 被共享,最终变成 5。
解决方法:
let(块级作用域): for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 0,1,2,3,4
}, 1000);
} for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(() => {
console.log(j); // 0,1,2,3,4
}, 1000);
})(i);
} React Hooks(如 useState、useEffect)依赖闭包机制来保存状态。
function Counter() {
const [count, setCount] = useState(0); // useState 返回的 count 被闭包记住
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1); // 回调函数可以访问最新的 count
}, 1000);
return () => clearInterval(timer);
}, []); // 依赖数组为空,闭包仅在首次渲染时创建
return {count};
}
useEffect 的回调函数是一个闭包,可以访问 count 和 setCount。[] 不正确,可能会导致闭包引用旧值(过期闭包问题)。| 特性 | 说明 |
|---|---|
| 定义 | 函数 + 它能访问的外部变量 |
| 作用 | 1. 封装私有变量 2. 函数工厂 3. 事件监听 & 回调 |
| 注意事项 | 1. 可能导致内存泄漏 2. 循环中的闭包陷阱 3. React Hooks 依赖闭包 |
| 经典问题 | for 循环 + setTimeout 问题 |
闭包是 JavaScript 的核心概念,理解它有助于编写更高效、更灵活的代码。