闭包是什么

69 阅读2分钟

一、闭包的核心定义(一句话概括)

闭包是函数嵌套中,内部函数引用了外部函数的变量/参数,且内部函数被外部访问时,形成的一种能保留外部函数作用域的特殊结构。

简单说:“内层函数记住了外层函数的变量,即使外层函数已经执行完毕”

二、用代码示例直观理解(高频考点)

function outer() {
  let count = 0; // 外部函数的局部变量
  
  // 内部函数,引用了outer的变量count
  function inner() {
    count++;
    console.log(count);
  }
  
  return inner; // 内部函数被返回(外部可访问)
}

// 执行外部函数,得到内部函数
const fn = outer(); 

// 调用内部函数(此时outer已执行完毕,但count仍被保留)
fn(); // 1
fn(); // 2
fn(); // 3

关键逻辑

  • outer执行后,本应销毁的count变量,因被inner引用且inner被外部fn持有,所以count被“闭包”保留下来。

三、闭包的本质(底层原理)

  • JavaScript中,函数执行时会创建作用域链:内部函数可以访问外部函数的变量,但外部不能访问内部。
  • 当内部函数被外部引用时,外部函数的作用域不会被垃圾回收机制销毁,形成闭包。
  • 闭包的核心是**“作用域的保留”**,而非“函数本身”。

四、闭包的主要作用(必答)

  1. 保存变量状态:让函数外部能间接访问函数内部的局部变量,且变量不会被销毁(如示例中的count累加)。
  2. 模块化封装:隐藏内部实现,只暴露需要的接口,避免全局变量污染。
    // 模块化示例:用闭包封装私有变量
    function createCounter() {
      let count = 0; // 私有变量,外部无法直接修改
      return {
        add: () => count++,
        get: () => count
      };
    }
    
    const counter = createCounter();
    counter.add();
    console.log(counter.get()); // 1(只能通过暴露的方法操作)
    

五、问题

1. 循环中的闭包陷阱

// 问题:以下代码点击每个按钮都会输出3,为什么?如何修复?
for (var i = 0; i < 3; i++) {
  document.getElementById(`btn${i}`).onclick = function() {
    console.log(i); // 始终输出3(i是全局变量,循环结束后为3)
  };
}

修复方案:用闭包保存每次循环的i

// 方法1:立即执行函数(IIFE)创建闭包
for (var i = 0; i < 3; i++) {
  (function(j) { // j接收当前i的值
    document.getElementById(`btn${i}`).onclick = function() {
      console.log(j); // 0、1、2(正确输出)
    };
  })(i);
}

// 方法2:用let声明(块级作用域,本质也是闭包)
for (let i = 0; i < 3; i++) { // let使每次循环的i是独立变量
  document.getElementById(`btn${i}`).onclick = function() {
    console.log(i); // 0、1、2
  };
}

2. 闭包的缺点与注意事项

  • 内存泄漏风险:闭包会保留外部函数的作用域,若长期不释放(如全局变量引用闭包),会占用过多内存,导致性能问题。
  • 解决办法:不再使用时,手动将引用闭包的变量设为null(如fn = null),让垃圾回收机制回收。

六、总结

“闭包是函数嵌套时,内部函数引用外部变量且被外部访问,导致外部作用域被保留的结构。

  • 作用是保存状态、模块化封装;
  • 常见场景如计数器、防抖节流、模块化开发;
  • 需注意内存泄漏问题,及时释放引用。