本文详细介绍闭包的概念、应用场景及一些坑

首先搞明白什么是闭包

简单说, 一个函数,能访问到它 “创建时” 所在作用域的变量,哪怕这个作用域已经执行结束,函数依然能 “抓着” 这些变量不放。
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中称之为函数式更新,函数式写法会接收 最新的状态值。


  1. 内层函数绑定了外层作用域的变量,导致该变量无法释放。很显然,内存泄漏无法避免。那么如何破?
    很简单: 变量 = 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

文毕