创见博客
JavaScript中的闭包
七崽爱吃小饼干2026/01/20阅读 0专栏 JavaScript

一、什么是闭包?

先给一个通俗易懂的定义: 闭包 是指一个函数能够访问并记住其外层(父级)函数作用域中的变量,即使外层函数已经执行完毕并退出。简单来说,闭包让“内部函数”可以“记住”并访问“外部函数”的变量,突破了函数作用域的限制。

核心原理(用代码举例)

javascript
// 外层函数
function outerFunc() {
  // 外层函数的局部变量(本应在outerFunc执行完后销毁)
  let outerVar = "我是外层变量";

  // 内部函数(闭包函数)
  function innerFunc() {
    // 内部函数访问外层函数的变量
    console.log(outerVar); 
  }

  // 返回内部函数,让它能在外部被调用
  return innerFunc;
}

// 执行外层函数,得到内部函数的引用
const myClosure = outerFunc();
// 此时outerFunc已经执行完毕,但innerFunc依然能访问outerVar
myClosure(); // 输出:我是外层变量

关键逻辑解释:

  1. outerFunc 执行后,按常理其内部的 outerVar 应该被垃圾回收机制销毁;
  2. 但因为 innerFunc 引用了 outerVar,且 innerFunc 被返回并赋值给 myClosure(在全局作用域中存在),所以 JS 引擎会保留 outerVar 的内存,形成闭包;
  3. 后续调用 myClosure() 时,依然能访问到 outerVar。

二、闭包的典型应用场景

闭包在实际开发中非常常用,以下是最典型的 4 个应用场景:

1. 保存私有变量(模块化/封装)

闭包可以创建“私有变量”——只能通过特定方法访问,无法直接修改,避免全局变量污染。

javascript
function createCounter() {
  // 私有变量:外部无法直接访问
  let count = 0;

  // 返回包含多个方法的对象,这些方法形成闭包,能访问count
  return {
    increment: () => count++, // 增加计数
    decrement: () => count--, // 减少计数
    getCount: () => count     // 获取当前计数
  };
}

// 创建计数器实例
const counter = createCounter();
console.log(counter.getCount()); // 0
counter.increment();
console.log(counter.getCount()); // 1
// 无法直接修改count(count是私有变量)
console.log(counter.count); // undefined

2. 柯里化(函数参数复用)

闭包可以实现函数柯里化,将多参数函数拆解为单参数函数,复用部分参数。

javascript
// 柯里化示例:计算a*b
function multiply(a) {
  // 闭包记住a的值
  return function(b) {
    return a * b;
  };
}

// 复用参数a=2
const double = multiply(2);
console.log(double(3)); // 6
console.log(double(5)); // 10

// 复用参数a=3
const triple = multiply(3);
console.log(triple(4)); // 12

3. 防抖/节流函数(性能优化)

防抖(debounce)和节流(throttle)是前端高频使用的性能优化手段,核心依赖闭包保存状态(如定时器ID、上次执行时间)。

javascript
// 防抖函数:频繁触发时,只执行最后一次
function debounce(fn, delay) {
  let timerId; // 闭包保存定时器ID
  return function(...args) {
    // 每次触发时清除之前的定时器
    clearTimeout(timerId);
    // 重新设置定时器
    timerId = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 示例:输入框输入后1秒执行查询
const input = document.querySelector('input');
input.addEventListener('input', debounce(function(e) {
  console.log('查询:', e.target.value);
}, 1000));

4. 循环中保存变量(经典问题)

在 for 循环中绑定事件时,闭包可以解决“循环变量共享”导致的结果不符合预期的问题。

javascript
// 问题场景:循环绑定点击事件,不加闭包会导致所有按钮点击都输出5
for (var i = 0; i < 5; i++) {
  // 用立即执行函数(IIFE)创建闭包,保存每次循环的i值
  (function(index) {
    const btn = document.createElement('button');
    btn.innerText = `按钮${index}`;
    btn.onclick = function() {
      alert(`你点击了按钮${index}`);
    };
    document.body.appendChild(btn);
  })(i);
}

总结

  1. 闭包的核心:内部函数访问外层函数的变量,且外层函数执行后变量不被销毁,本质是作用域链的特殊表现;
  2. 核心应用:封装私有变量、函数柯里化、防抖/节流、解决循环变量共享问题;
  3. 注意事项:闭包会保留外层变量的内存,滥用可能导致内存泄漏,需在不需要时手动释放(如将闭包引用赋值为 null)。
评论
0/100