一、闭包的定义
闭包(Closure) 是指 有权访问另一个函数作用域中的变量的函数 。
简单来说,就是函数可以"记住"定义时的作用域,而不是调用时的作用域。
            
            
              scss
              
              
            
          
          function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  }
}
const fn = outer();
fn(); // 1
fn(); // 2这里
inner就是闭包,它可以访问outer的变量count。
二、闭包的形成条件
闭包产生通常需要三个条件:
- 函数嵌套:有函数内部函数。
- 内部函数引用外部变量。
- 外部函数返回内部函数或将内部函数传出。
三、闭包的作用
- 保护变量私有化(封装)
            
            
              javascript
              
              
            
          
          function createCounter() {
  let count = 0;
  return {
    increment() { count++; return count; },
    decrement() { count--; return count; }
  }
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.decrement()); // 0外部无法直接访问
count,只能通过提供的方法操作。
- 函数工厂 / 动态生成函数
            
            
              javascript
              
              
            
          
          function multiplyBy(n) {
  return function(x) {
    return x * n;
  }
}
const double = multiplyBy(2);
console.log(double(5)); // 10- 保持状态(常用于异步、事件回调)
四、闭包的面试常考点
1. 循环中的闭包问题
            
            
              javascript
              
              
            
          
          for (var i = 0; i < 3; i++) {
  setTimeout(function() { console.log(i); }, 100);
}
// 输出:3 3 3原因 :var 是函数作用域,闭包引用的是同一个 i。
解决方案:
- 使用 let(块级作用域):
            
            
              javascript
              
              
            
          
          for (let i = 0; i < 3; i++) {
  setTimeout(function() { console.log(i); }, 100);
}
// 输出:0 1 2- 或使用立即执行函数(IIFE):
            
            
              css
              
              
            
          
          for (var i = 0; i < 3; i++) {
  (function(i){
    setTimeout(function() { console.log(i); }, 100);
  })(i);
}2. 闭包与内存泄漏
闭包会 引用外部作用域变量 ,可能导致 垃圾回收无法释放。
- 避免过度使用全局闭包。
- 在不需要时,手动清理引用。
3. 面试题示例
题1:输出结果?
            
            
              ini
              
              
            
          
          function makeFunc() {
  let name = "Yo Yo";
  return function() {
    console.log(name);
  }
}
const func = makeFunc();
func();答案 :Yo Yo
因为闭包保持了对
name的引用。
题2:循环闭包输出?
            
            
              scss
              
              
            
          
          var funcs = [];
for (var i = 0; i < 3; i++) {
  funcs[i] = function() { console.log(i); };
}
funcs[0](); funcs[1](); funcs[2]();答案 :3 3 3
var是函数作用域,闭包引用的是同一个i。
题3:使用闭包实现私有变量
            
            
              javascript
              
              
            
          
          function Counter() {
  let count = 0;
  this.increment = function() { count++; return count; }
  this.decrement = function() { count--; return count; }
}
const c = new Counter();
console.log(c.increment()); // 1题4:闭包结合 setTimeout
            
            
              css
              
              
            
          
          for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}答案 :0 1 2
let作用域绑定每次循环的i。
五、闭包引起内存泄漏问题
1 为什么闭包会造成内存无法释放?
闭包之所以"强大",是因为它让内部函数持续引用 外部作用域中的变量。
但也正因为这个引用存在,即使外层函数已经执行完毕,外层变量仍然被保存在内存中,不能被垃圾回收(GC)释放。
示例
            
            
              javascript
              
              
            
          
          function outer() {
  let bigData = new Array(1000000).fill('数据');
  return function inner() {
    console.log(bigData.length);
  }
}
const fn = outer();
// outer 已经执行完毕,但 bigData 仍被 inner 引用,无法回收这里
bigData占用大量内存,而fn一直持有引用,GC 就不会释放。
2 在不需要时,如何"手动清理引用"
关键思路:让闭包不再引用外部变量。
✅ 方式 1:将闭包变量置为 null
        
            
            
              javascript
              
              
            
          
          function outer() {
  let bigData = new Array(1000000).fill('数据');
  return function inner() {
    console.log(bigData.length);
    bigData = null; // 手动解除引用
  }
}
const fn = outer();
fn(); // 使用一次调用
fn后手动将bigData = null,让 GC 能识别为可回收。
✅ 方式 2:将闭包函数引用置为 null
        
            
            
              php
              
              
            
          
          let fn = (function() {
  let data = { name: 'Yo Yo' };
  return function() {
    console.log(data);
  }
})();
fn();      // 使用
fn = null; // 手动解除引用,闭包随之释放一旦
fn没有被引用,整个闭包作用域都会被 GC。
✅ 方式 3:避免不必要的全局闭包
不要让闭包函数挂在全局对象上,比如:
            
            
              javascript
              
              
            
          
          window.fn = (function() {
  let cache = [];
  return function() { cache.push(Date.now()); }
})();改进:
            
            
              javascript
              
              
            
          
          (function() {
  let cache = [];
  document.body.addEventListener('click', function() {
    cache.push(Date.now());
  });
})();只在局部使用闭包,事件解绑或模块销毁时自然会释放。
✅ 方式 4:组件或页面卸载时清理(在框架中)
比如 React、Vue 中使用闭包保存状态时,在组件卸载阶段清理:
React 示例
            
            
              javascript
              
              
            
          
          useEffect(() => {
  let timer = setInterval(() => console.log('run'), 1000);
  return () => clearInterval(timer); // 清理引用
}, []);如果闭包里引用了外部变量(如 DOM、定时器),必须在 cleanup 阶段清理,否则内存会持续增长。
3、判断闭包导致的内存泄漏的常见迹象
- 控制台内存面板中,执行某函数后内存不下降;
- 页面关闭某功能后,内存占用仍持续增加;
- 浏览器 Performance 记录中,Detached DOM 节点未被释放。
总结要点
- 
闭包 = 函数 + 对外部变量的引用 
- 
好处: - 数据私有化
- 保存状态
- 动态函数生成
 
- 
注意: - 循环变量问题
- 内存泄漏
- 不要滥用全局闭包