什么是javascript闭包_它有什么实际用途【教程】

闭包是JavaScript中内部函数访问外部变量并逃逸执行时自动形成的运行时行为;需同时满足函数嵌套、变量捕获、逃逸执行三条件;用于封装私有状态、解决循环索引问题,但可能引发内存泄漏。

闭包 是 JavaScript 中函数“记住自己出生地”

的能力:当一个函数被定义在另一个函数内部,并且**访问了外部函数的变量**,同时这个内部函数**被返回或传到别处执行**,它就形成了闭包。 它不是语法糖,也不是设计模式,而是一种运行时行为——只要满足这三个条件,闭包就自动产生。

怎么一眼识别闭包?看这三步

闭包不是写出来的,是“跑出来”的。判断一个函数是不是闭包,只看三点:

  • 函数嵌套:内部函数定义在外部函数体内
  • 变量捕获:inner 函数体里直接用了 outer 的局部变量(比如 let countconst config
  • 逃逸执行:这个 inner 被返回、赋值给变量、传给 setTimeout 或事件监听器,**脱离了原始调用环境**

缺一不可。比如下面这个就不是闭包:

function outer() {
  const x = 1;
  function inner() {
    console.log('I ignore x'); // 没引用外部变量
  }
  return inner;
}

inner 虽然嵌套,但没捕获任何自由变量,引擎不会为它保留作用域链。

最常用的实际用途:封装私有状态

JavaScript 没有 #private 字段(ES2025+ 才有,且不兼容旧环境),而闭包是长期稳定、全版本兼容的私有变量方案。

典型写法:

function createCounter() {
  let count = 0; // 外部变量 → 被闭包保护
  return {
    increment() { count++; },
    getCount() { return count; }
  };
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1

这里 count 对外完全不可见,连 counter.count 都是 undefined。所有修改必须走暴露的方法。

  • 避免全局污染:变量锁死在函数作用域内
  • 防止误改:外部无法绕过逻辑直接赋值
  • 适合配置模块、单例缓存、状态管理器等场景

循环中绑定正确索引:别再用 var

这是新手踩坑最多的地方。下面这段代码会输出 5 个 5

for (var i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}

因为 var 是函数作用域,i 全局共享;所有回调共用同一个 i,等真正执行时循环早已结束,i === 5

用闭包修复(ES5 方式):

for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 100);
  })(i);
}

更现代、更推荐的写法(ES6+):

for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 100);
}

let 声明每次迭代都创建新绑定,本质就是语言层帮你做了闭包封装——你不用手动写 IIFE,但原理一样。

容易被忽略的代价:内存泄漏风险

闭包让变量“活”得更久——只要内部函数还存在,它引用的所有外部变量就无法被垃圾回收。

  • 如果闭包持有 DOM 节点、大数组、或整个对象树,而你又忘了清理(比如没移除事件监听器),内存就悄悄涨上去了
  • 在长生命周期对象(如页面级模块)中频繁创建闭包,尤其要检查是否无意保留了对大对象的引用
  • 调试时可借助 Chrome DevTools 的 Memory > Heap Snapshot,筛选 Closure 类型,看哪些变量被意外滞留

闭包本身不是问题,问题在于“该释放时不释放”。它像一把没有保险栓的刀——好用,但得清楚自己握着哪一头。