什么是闭包,JavaScript闭包详解
闭包(Closure)详解
闭包(Closure)是 JavaScript 中一个非常重要的概念,它允许函数访问并记住其词法作用域(lexical scope)中的变量,即使该函数在其作用域外执行。简单来说,闭包是一个函数 + 它能够访问的外部变量。
1. 闭包的基本概念
(1)词法作用域(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
,这就是闭包。
(2)闭包的核心特点
- 函数可以访问其定义时的作用域,即使该函数在定义的作用域外执行。
- 闭包会保留对外部变量的引用,而不是复制值。
2. 闭包的常见用途
(1)封装私有变量
闭包可以模拟私有变量(类似 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
访问。
(2)函数工厂(Function Factory)
闭包可以动态生成函数。
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
的值。
(3)事件监听 & 回调
闭包常用于事件监听和异步回调。
function setupButton() {const button = document.getElementById("myButton");let clickCount = 0;button.addEventListener("click", () => {clickCount++;console.log(`Button clicked ${clickCount} times`);});
}setupButton();
- 回调函数
() => { ... }
是一个闭包,可以访问clickCount
。
3. 闭包的注意事项
(1)内存泄漏
闭包会保留对外部变量的引用,可能导致内存无法释放。
function heavyOperation() {const bigData = new Array(1000000).fill("data"); // 大数据return function() {console.log(bigData.length); // 闭包引用 bigData};
}const fn = heavyOperation();
fn(); // 即使 heavyOperation 执行完毕,bigData 仍然被闭包引用,无法被垃圾回收
解决方案:在不需要时手动解除引用(如 fn = null
)。
(2)循环中的闭包陷阱
在 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); }
- 使用 IIFE(立即执行函数):
for (var i = 0; i < 5; i++) {(function(j) {setTimeout(() => {console.log(j); // 0,1,2,3,4}, 1000);})(i); }
4. 闭包与 React Hooks
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 <div>{count}</div>;
}
useEffect
的回调函数是一个闭包,可以访问count
和setCount
。- 如果依赖数组
[]
不正确,可能会导致闭包引用旧值(过期闭包问题)。
5. 总结
特性 | 说明 |
---|---|
定义 | 函数 + 它能访问的外部变量 |
作用 | 1. 封装私有变量<br>2. 函数工厂<br>3. 事件监听 & 回调 |
注意事项 | 1. 可能导致内存泄漏<br>2. 循环中的闭包陷阱<br>3. React Hooks 依赖闭包 |
经典问题 | for 循环 + setTimeout 问题 |
闭包是 JavaScript 的核心概念,理解它有助于编写更高效、更灵活的代码。🚀