闭包详解
本文详细介绍闭包的概念、应用场景及一些坑
首先搞明白什么是闭包
简单说, 一个函数,能访问到它 “创建时” 所在作用域的变量,哪怕这个作用域已经执行结束,函数依然能 “抓着” 这些变量不放。
react代码演示:
import { useEffect, useState } from "react";
export default function TestPage() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
const newVal = count + 1;
console.log("newVal", newVal);
setCount(newVal);
}, 2000);
return () => {
clearInterval(timer);
};
}, []);
return <div>当前数值: {count}</div>;
}
通过上面代码,我们在页面加载后设置了一个定时器。定时器每次执行一个数据自增的操作,逻辑很简单,获取当前count,+1,赋值,无线操作之。
页面测试效果:
count值,居然没变…
怎么理解?
逻辑如此之清晰,居然出现了意料之外的效果,为什么?页面加载完成后,执行useEffect,其回调函数参数中,设置了一个定时器函数,此时,该定时器回调函数同useEffect作用域形成了一个闭包。即使 count 在后续的状态更新中发生了变化,setInterval 中的回调函数仍然会使用捕获的初始值。这就是典型的闭包.
解决方案
针对上面的场景,可以如下操作解决之:
import { useEffect, useState } from "react";
export default function TestPage() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount((val) => {
const newVal = val + 1;
console.log("newVal", newVal);
return newVal;
});
}, 2000);
return () => {
clearInterval(timer);
};
}, []);
return <div>当前数值: {count}</div>;
}
react中称之为函数式更新,函数式写法会接收 最新的状态值。
- 坑
内层函数绑定了外层作用域的变量,导致该变量无法释放。很显然,内存泄漏无法避免。那么如何破?
很简单: 变量 = null
应用场景
变量私有化。早期模块化的实践方案,避免变量名污染。
防抖、节流函数
自定义 Hooks(复用状态逻辑)
// 自定义Hook:封装计数器逻辑 function useCounter(initialValue = 0) { // 1. Hooks 作用域内的变量:count(状态)、setCount(方法) const [count, setCount] = useState(initialValue); // 2. 定义方法:increment 是一个闭包函数! const increment = () => { // 能访问外层 Hooks 作用域的 count/setCount —— 闭包的关键 setCount(count + 1); }; // 3. 返回状态和闭包方法给组件 return { count, increment }; } // 组件中使用自定义Hook function CounterComponent() { // 拿到 Hooks 返回的 count 和 increment(闭包函数) const { count, increment } = useCounter(); return ( <div> <p>计数:{count}</p> <button onClick={increment}>+1</button> </div> ); }应该这么讲,自定义 Hooks 之所以能正常工作,核心就是闭包在 “兜底”。
函数柯里化(逻辑复用,参数复用)
// 柯里化:计算商品价格(先传折扣,再传价格,闭包保存折扣) function calcPrice(discount) { // 闭包持有discount return (price) => price * discount; } // 复用9折逻辑 const calc9折 = calcPrice(0.9); console.log(calc9折(100)); // 90 console.log(calc9折(200)); // 180 // 复用8折逻辑 const calc8折 = calcPrice(0.8); console.log(calc8折(100)); // 80
文毕
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
