闭包

32 阅读1分钟

闭包:函数嵌套函数,内部函数访问外部作用域,变量永不释放!

function createCounter() {
  let count = 0; // ? 被记住的变量
  return function() {
    count++; // ? 闭包诞生!
    return count;
  };
}

以下是三个闭包实战场景的代码实现

1. 防抖按钮(连续点击只触发一次)

// 防抖函数:连续点击时只在最后一次执行
function debounce(fn, delay) {
  let timer = null; // ? 闭包保存的定时器
  
  return function() {
    clearTimeout(timer); // 清除上次的定时器
    timer = setTimeout(() => {
      fn.apply(this, arguments); // ✅ 延迟执行目标函数
    }, delay);
  };
}

// 使用示例
const searchButton = document.getElementById('search-btn');
searchButton.addEventListener('click', debounce(() => {
  console.log('搜索请求已发送!'); // ? 实际业务请求
}, 500));

2. 私有变量(外部无法修改内部数据)

function createBankAccount(initialBalance) {
  let balance = initialBalance; // ? 闭包保护的私有变量
  
  return {
    deposit: (amount) => {
      balance += amount;
      console.log(`存款成功,余额:${balance}`);
    },
    withdraw: (amount) => {
      if (amount > balance) {
        console.log("余额不足!");
        return;
      }
      balance -= amount;
      console.log(`取款成功,余额:${balance}`);
    },
    getBalance: () => {
      return balance; // ? 外部只能通过方法访问
    }
  };
}

// 使用示例
const myAccount = createBankAccount(1000);
myAccount.deposit(500); // ✅ 存款成功,余额:1500
myAccount.withdraw(200); // ✅ 取款成功,余额:1300
console.log(myAccount.balance); // ❌ undefined (无法直接访问)
console.log(myAccount.getBalance()); // ✅ 1300 (安全访问)

3. 循环陷阱修复(setTimeout正确输出索引)

// 错误版本:输出全是5
for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i); // ❌ 输出:5,5,5,5,5
  }, 100);
}

// 闭包修复版本:正确输出0-4
for (var i = 0; i < 5; i++) {
  (function(j) { // ? IIFE创建闭包作用域
    setTimeout(() => {
      console.log(j); // ✅ 输出:0,1,2,3,4
    }, 100);
  })(i); // 立即传入当前i值
}

// 现代方案:使用let块级作用域
for (let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i); // ✅ 输出:0,1,2,3,4
  }, 100);
}