JS 中的闭包详解(以 React 为例)

闭包(Closure)是 JavaScript 中非常重要的概念,理解闭包对于掌握作用域、数据保护、回调、React 组件开发等都有极大帮助。

一、什么是闭包?

闭包是指函数能够“记住”并访问它定义时的词法作用域,即使这个函数在其词法作用域之外被调用。

通俗理解:闭包就是“函数+定义该函数的环境”。

形成条件

  • 函数嵌套函数
  • 内部函数引用了外部函数的变量
  • 外部函数返回了内部函数或将其传递到外部

二、闭包的经典例子

1. 基础例子

1
2
3
4
5
6
7
8
9
10
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

2. 保护变量不被外部直接访问

1
2
3
4
5
6
7
8
9
10
11
function createPerson(name) {
let age = 0;
return {
getName: () => name,
getAge: () => age,
grow: () => age++,
};
}
const tom = createPerson("Tom");
tom.grow();
console.log(tom.getAge()); // 1

3. 循环中的闭包问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const btns = [];
for (var i = 0; i < 3; i++) {
btns[i] = function () {
console.log(i);
};
}
btns[0](); // 3
// 解决:用 let 或 IIFE
for (let i = 0; i < 3; i++) {
btns[i] = function () {
console.log(i);
};
}
btns[0](); // 0

三、闭包在 React 中的应用

1. 事件处理中的闭包

1
2
3
4
5
6
7
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1); // handleClick 闭包中“记住”了 count
}
return <button onClick={handleClick}>{count}</button>;
}

2. useEffect 与闭包陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 闭包捕获了初始 count,可能不是最新值
}, 1000);
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
}
// 正确写法:
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + 1); // 使用函数式 setState,避免闭包陷阱
}, 1000);
return () => clearInterval(id);
}, []);

3. 自定义 Hook 中的闭包

1
2
3
4
5
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount((c) => c + 1);
return { count, increment };
}

四、闭包的常见用途

  • 数据私有化与封装
  • 工厂函数、柯里化
  • 事件监听、回调
  • React 组件状态、Hook

五、闭包的注意事项

  • 闭包会导致变量常驻内存,注意内存泄漏
  • React 中闭包常见“旧值陷阱”,可用函数式 setState 规避

六、总结

  • 闭包是函数+定义环境的组合,能访问外部作用域变量
  • React 事件、Hook、定时器等大量用到闭包
  • 理解闭包有助于写出更健壮的前端代码

以上内容仅供参考,请结合实际情况具体分析