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

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

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

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

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

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


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

业务需求

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

  • 每次点击数字 +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)
函数工厂 让函数"记住"创建时的参数
模块封装 模拟私有属性和方法
循环 + 异步 锁定每次迭代的变量值

一句话记住闭包

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

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

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

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

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

相关推荐
考虑考虑6 小时前
数据库中的EXISTS
运维·数据库·后端
郑州光合科技余经理7 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
毅炼7 小时前
Rover-Suite 开源发布:轻量服务注册中心与网关一体化方案
java·后端·系统架构·gateway
苏三说技术12 小时前
为什么越来越多人用OpenWiki?
后端
IT_陈寒12 小时前
Java里用Stream.parallel()翻车实录,这性能还不如单线程
前端·人工智能·后端
Bs_MoneyMagnet12 小时前
基于springboot+vue的生态果园采摘预约系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·毕业设计·计算机毕业设计
码事漫谈12 小时前
FDE 的起源、形态与真实边界
后端
ServBay13 小时前
Grok Bot 还是 OpenClaw?聊聊真正能替人打杂的 AI 智能体
后端·aigc·grok
掘金挖土13 小时前
前端手摸手跑路之 AI 应用开发(五)
前端·后端
弈栈录13 小时前
LangGraph 入门:用状态机设计可靠的 Agent 工作流
后端·程序员