深入理解闭包

62 阅读2分钟

在 JavaScript 中,闭包(Closure) 是一个非常重要的核心概念,它允许函数「记住」并「访问」自己定义时的词法作用域(Lexical Scope),即使这个函数在其原始作用域之外执行。闭包的本质是函数与其引用的外部变量的捆绑关系


🔍 闭包的核心特征

  1. 突破作用域链
    函数可以访问定义时所在作用域的变量,即使外层函数已执行完毕。

  2. 持久化变量
    被闭包引用的变量不会被垃圾回收机制回收,除非闭包本身被销毁。


🛠️ 闭包的形成条件

function outer() {
  const outerVar = "我在外层!";  // 外层变量
  
  function inner() {            // 内层函数
    console.log(outerVar);      // 引用外层变量
  }
  
  return inner;                 // 返回内层函数
}

const closure = outer();        // outer 已执行完毕
closure();                      // 输出:"我在外层!" ✅
  1. 嵌套函数:存在函数嵌套(如 inner 嵌套在 outer 中)
  2. 引用外部变量:内层函数引用了外层函数的变量(outerVar
  3. 外部暴露:内层函数被导出到外层作用域(通过 return 或赋值给全局变量)

🌟 闭包的经典用途

1. 封装私有变量(模块模式)

function createCounter() {
  let count = 0;  // 私有变量
  
  return {
    increment: () => ++count,
    getCount: () => count
  };
}

const counter = createCounter();
counter.increment();  // 1
counter.getCount();   // 1
  • 外部无法直接修改 count,只能通过暴露的方法操作。

2. 保存状态(如事件回调)

function setupButton() {
  const button = document.querySelector("#myBtn");
  let clickCount = 0;

  button.addEventListener("click", () => {
    clickCount++;
    console.log(`点击了 ${clickCount} 次`);
  });
}
  • 事件回调函数通过闭包记住了 clickCount 的状态。

3. 函数工厂

function createMultiplier(factor) {
  return function(x) {
    return x * factor;
  };
}

const double = createMultiplier(2);
console.log(double(5));  // 10 ✅

⚠️ 闭包的注意事项

  1. 内存泄漏风险
    被闭包引用的变量不会被回收,需手动解除引用:

    function leak() {
      const bigData = new Array(1000000).fill("🚀");
      return () => bigData;  // 持续引用大数据
    }
    const holder = leak();  // 即使不再使用,bigData 仍存在内存中
    
  2. 循环中的闭包陷阱

    for (var i = 0; i < 3; i++) {
      setTimeout(() => console.log(i), 100);  // 输出 3 3 3 ❌
    }
    

    解决方案:使用 let 或 IIFE(立即执行函数)创建新作用域:

    for (let i = 0; i < 3; i++) {  // let 创建块级作用域
      setTimeout(() => console.log(i), 100);  // 0 1 2 ✅
    }
    

🌐 闭包的底层原理

JavaScript 引擎通过「作用域链(Scope Chain)」实现闭包:

  1. 函数在定义时,会保存其所在作用域的引用([[Environment]] 属性)。
  2. 当函数执行时,即使外层函数已销毁,其作用域仍被内层函数引用,因此不会被垃圾回收。

💡 一句话总结

闭包 = 函数 + 它捕获的外部变量。它是 JavaScript 实现模块化、状态保持和高级编程模式的基石。