Skip to content

闭包是什么?符合哪些特征才算是闭包?请举例。

非常棒的问题 ⚡️这说明你已经在深入理解 JavaScript 的核心机制之一 —— 闭包(Closure)

我们来从「定义 → 特征 → 原理 → 示例」四个方面深入讲解 👇


🧠 一、闭包是什么(定义)

闭包(Closure)是指:

一个函数可以访问其词法作用域(lexical scope)之外的变量的能力。

简单说: 👉 当内部函数引用了外部函数的变量,并且外部函数已经执行完毕,此时依然能访问这些变量,就形成了闭包。


🔍 二、闭包的三个必要条件(特征)

特征说明
1️⃣ 嵌套函数必须在一个函数内部再定义函数。
2️⃣ 引用了外部变量内部函数必须使用了外部函数的变量。
3️⃣ 外部函数执行完但变量仍被引用外部函数执行结束后,变量仍保存在内存中(未被 GC 回收)。

满足以上三个条件,就形成了闭包。


🧩 三、为什么闭包能存在(原理)

JS 的作用域是 词法作用域(lexical scope),由代码定义位置决定。 当一个函数被定义时,它会“记住”当时的作用域链(scope chain)。 即使函数在外部被调用,也能访问它定义时的变量环境。

这就是闭包的底层实现原理。


💡 四、举例说明

✅ 示例 1:最典型的闭包

js
function createCounter() {
  let count = 0; // 外部变量

  return function() {
    count++; // 内部函数引用了外部变量
    return count;
  };
}

const counter = createCounter();

console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

🔍 分析:

  • createCounter 执行后,正常来说变量 count 应该被销毁;
  • 但内部函数仍在使用 count
  • 因此 JS 引擎保留了 count 的环境,这就是闭包。

✅ 示例 2:循环绑定常见问题(闭包的应用)

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

原因:var 没有块级作用域,所有回调共享一个 i

✅ 用闭包修复:

js
for (var i = 0; i < 3; i++) {
  ((j) => {
    setTimeout(() => console.log(j), 1000);
  })(i);
}
// 输出:0 1 2

这里 (j => {...})(i) 创建了闭包,把每次的 i 值保存起来。


✅ 示例 3:模块化应用

js
function createModule() {
  let secret = 'my-secret';
  return {
    getSecret() {
      return secret;
    },
    setSecret(newVal) {
      secret = newVal;
    }
  };
}

const module = createModule();
console.log(module.getSecret()); // 'my-secret'
module.setSecret('updated');
console.log(module.getSecret()); // 'updated'

这是闭包在封装私有变量时的常见用法。


🧩 五、闭包的注意点(易错与优化)

问题原因解决方法
内存泄漏长期保存外部变量引用手动释放引用(obj = null
调试困难变量被保留但不销毁谨慎使用闭包做缓存
性能问题作用域链查找更深避免深层嵌套函数

一句话总结:

闭包 = 函数 + 其定义时的词法环境。 它让函数在“外部执行时”,仍能记住“内部定义时”的变量。


是否希望我帮你画一个「闭包执行过程」的内存示意图?(展示作用域链的关系)