闭包到底有什么用?别只背概念,看完秒懂实际业务场景

闭包到底有什么用?别只背概念,看完秒懂实际业务场景

如果你搜过"什么是闭包",大概率看到过这样的定义:

"闭包是指有权访问另一个函数作用域中的变量的函数。"

说实话,第一次看到这句话的时候,我脑子里只有三个字:这啥啊?

今天咱们不背概念,直接用真实业务场景告诉你:闭包到底解决了什么问题、在什么情况下你会主动用它。


场景一:计数器 ------ 不想让外部随便改数据

业务需求

做一个按钮点击计数器,要求:

  • 每次点击数字 +1
  • 外部不能直接把计数清零或篡改

❌ 不用闭包的做法(有坑)

ini 复制代码
let count = 0;

function increment() {
  count++;
  console.log(count);
}

问题很明显:count 是个全局变量,任何人都能直接改:

ini 复制代码
count = 0; // 随便清零,防不胜防

✅ 用闭包的做法

scss 复制代码
function createCounter() {
  let count = 0; // 私有变量,外部碰不到

  return function () {
    count++;
    console.log(count);
    return count;
  };
}

const counter = createCounter();

counter(); // 1
counter(); // 2
counter(); // 3

为什么这是闭包?

createCounter 执行完后,它内部的 count 本该被销毁。但因为返回的函数引用了 count,JavaScript 引擎会把它保留在内存中 ------ 这就是闭包的核心机制。

实际价值:

  • count 对外完全不可见,只能通过 counter() 修改
  • 这是 JS 中实现私有变量的唯一原生方式

场景二:防抖(Debounce)------ 搜索框输入不疯狂请求接口

业务需求

搜索框输入时调用接口,但用户打字很快,不能每敲一个字就请求一次,要等用户停下来再发请求。

完整实现

javascript 复制代码
function debounce(fn, delay) {
  let timer = null; // 闭包变量:记录定时器

  return function (...args) {
    clearTimeout(timer);   // 每次触发都清除上一个定时器
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 使用
const search = debounce((keyword) => {
  console.log('请求接口:', keyword);
}, 500);

input.addEventListener('input', (e) => {
  search(e.target.value);
});

为什么必须用闭包?

timer 需要跨多次函数调用来保存状态。如果不用闭包,你得把 timer 放在全局,那多个防抖函数就会互相干扰。

闭包在这里的作用:给每个 debounce 调用创建一个独立的"小房间",timer 住在里面,外面进不去。


场景三:函数工厂 ------ 批量生成相似但独立的功能

业务需求

页面上有三种按钮:普通用户、VIP、管理员,点击后弹出不同的提示语。

❌ 不用闭包的写法

ini 复制代码
function handleClick(role) {
  if (role === 'user') {
    alert('欢迎普通用户');
  } else if (role === 'vip') {
    alert('欢迎尊贵的VIP');
  } else if (role === 'admin') {
    alert('管理员你好');
  }
}

问题:每次点击都要判断角色,而且如果角色很多,这个函数会越来越臃肿。

✅ 用闭包的写法

ini 复制代码
function createGreeting(role) {
  const messages = {
    user: '欢迎普通用户',
    vip: '欢迎尊贵的VIP',
    admin: '管理员你好'
  };

  return function () {
    alert(messages[role]);
  };
}

// 提前生成好三个函数
const greetUser = createGreeting('user');
const greetVip = createGreeting('vip');
const greetAdmin = createGreeting('admin');

// 绑定事件
userBtn.onclick = greetUser;   // 点击直接弹对应消息
vipBtn.onclick = greetVip;
adminBtn.onclick = greetAdmin;

实际价值:

  • 每个函数"记住"了自己的 role,点击时不需要再传参
  • 代码更干净,性能更好(不用每次判断)

场景四:模块化封装 ------ 早期 JS 没有 class 时的"类"

在 ES6 之前,JS 没有 classprivate,前端是怎么写模块的?靠闭包。

javascript 复制代码
const UserModule = (function () {
  let users = []; // 私有数据,外部无法直接访问

  return {
    add(name) {
      users.push(name);
      console.log(`${name} 已添加`);
    },
    list() {
      return [...users]; // 返回副本,防止外部直接修改原数组
    },
    count() {
      return users.length;
    }
  };
})();

UserModule.add('张三');
UserModule.add('李四');
console.log(UserModule.list()); // ['张三', '李四']
console.log(UserModule.count()); // 2

// users 外部完全访问不到
console.log(UserModule.users); // undefined

这就是早期 jQuery 插件、各种 JS 库的封装方式。 ​ 闭包让你可以模拟出"私有属性",这是公开变量做不到的。


场景五:循环 + 异步的经典坑(也是面试常客)

问题代码

css 复制代码
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 输出什么?
  }, 100);
}

很多人以为是 0, 1, 2,实际输出是:

复制代码
3
3
3

原因:setTimeout 回调执行时,for 循环已经跑完了,i 的最终值是 3

解决方案一:用闭包(IIFE)

javascript 复制代码
for (var i = 0; i < 3; i++) {
  (function (j) {
    setTimeout(() => {
      console.log(j); // 0, 1, 2 ✅
    }, 100);
  })(i);
}

每次循环创建一个 IIFE(立即执行函数),j 通过闭包"锁住"了当时的 i 值。

解决方案二:用 let(本质也是闭包)

ini 复制代码
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // 0, 1, 2 ✅
  }, 100);
}

let 在每次迭代时都会创建一个新的块级作用域绑定,本质上就是引擎帮你做了闭包的事。


总结:闭包到底解决了什么问题?

场景 闭包解决的问题
计数器 / 私有变量 数据封装,防止外部随意篡改
防抖 / 节流 跨调用保存状态(定时器ID)
函数工厂 让函数"记住"创建时的参数
模块封装 模拟私有属性和方法
循环 + 异步 锁定每次迭代的变量值

一句话记住闭包

闭包就是一个函数 + 它能访问的外部变量,打包在一起。

它的核心价值是:让数据活得比创建它的函数更久,同时对外隐藏。

不用死记硬背定义,下次写代码时想想:

"我需要一个函数记住某些数据,但不想让别人碰到这些数据。"

------ 这时候你就在用闭包了。

相关推荐
神奇小汤圆3 小时前
深入理解 TiDB 分布式事务:Percolator 模型与工程实践
后端
神奇小汤圆3 小时前
Flink SQL 从编写到提交运行的全过程解析
后端
饼干哥哥3 小时前
Codex 必改的8 个基础配置
前端·人工智能·后端
lingran__3 小时前
Linux 基础常用指令万字详解(上)|文件目录命令,结合底层原理剖析
linux·运维·服务器·后端·centos·linux基础指令
情可轻 秦任之4 小时前
ASP.NET页面优化,性能提升8倍的方法
后端·asp.net
王中阳Go4 小时前
用TRAE Work批量优化学员简历,原来2天的活现在2小时就干完了
后端·面试·agent
Zane19944 小时前
同一个 list 为什么能被两个 for 循环同时遍历?一文讲透可迭代对象与迭代器的真相
后端·python
他们叫我秃子4 小时前
前端开发转 Go 全栈(四):代码写在前面,却要最后执行?我终于搞懂了 defer
前端·后端·go
Csvn5 小时前
📊 SQL 入门 Day 14:聚合窗口函数 — 让 SUM / AVG 也能"滑动"起来
后端·sql