一、什么是闭包用一句话概括闭包就是“函数” “它能访问的外部变量环境”。在 JavaScript 中函数内部可以访问外部的变量但外部无法访问函数内部的变量。当一个内部函数被返回到外部或者被传递到其他地方时它不仅带走了自己的代码还“打包”带走了它出生时所在的外部变量。这个“打包带走”的动作和结果就是闭包。二、为什么需要闭包实际开发场景在实际开发中我们通常用闭包来实现两个目的保护变量不被污染实现私有变量让变量的值一直保存在内存中状态保持。三、贴近实际开发的例子场景一实现一个“计数器”状态保持假设你在做一个购物车需要记录用户加了多少次商品。如果用全局变量很容易被别人不小心改掉。用闭包就可以完美解决functioncreateCounter(){letcount0;// 这个变量被“保护”起来了外面直接访问不到// 返回一个对象里面包含操作 count 的方法return{increment:function(){count;returncount;},getCount:function(){returncount;}};}constmyCartcreateCounter();console.log(myCart.getCount());// 0console.log(myCart.increment());// 1console.log(myCart.increment());// 2// console.log(count); // 报错外部根本找不到 count 变量原理解析createCounter执行完后按理说里面的count变量应该被销毁。但因为返回的increment和getCount函数还在使用countJavaScript 引擎就会把count连同这两个函数一起“闭包”起来保存在内存中。场景二React/Vue 中常见的“防抖函数”封装工具// 封装一个防抖工具函数functiondebounce(fn,delay){lettimernull;// 闭包变量用来存定时器returnfunction(...args){// 每次触发都清除上一次的定时器if(timer)clearTimeout(timer);// 重新设置定时器timersetTimeout((){fn.apply(this,args);},delay);};}// 实际使用consthandleSearchdebounce((keyword){console.log(发送请求:,keyword);},500);// 用户快速输入handleSearch(a);handleSearch(ab);handleSearch(abc);// 只有这个会在 500ms 后真正执行**原理解析**这里的timer变量就是闭包的核心。每次调用handleSearch它都能“记住”上一次的timer是什么从而做到清除和重置。如果没有闭包timer每次都会变成一个新的局部变量防抖就失效了。四、避坑指南闭包虽然好用但有一个致命缺点内存泄漏。因为闭包会把变量一直留在内存里如果滥用闭包比如在循环里疯狂创建闭包又不释放会导致网页越来越卡甚至崩溃。用完闭包后如果不再需要可以手动把引用它的变量赋值为null比如myCart null告诉垃圾回收器可以清理了。不要为了用闭包而用闭包现代 JavaScript 有了class和模块系统import/export很多以前需要闭包实现的私有变量现在有更优雅的写法。方案一ES6 Class # 私有字段最推荐 ⭐在 ES2022 中JavaScript 引入了真正的语法级私有字段只需要在变量名前面加一个#号。classShoppingCart{// 使用 # 声明私有变量外部绝对无法访问#count0;increment(){this.#count;returnthis.#count;}getCount(){returnthis.#count;}}constmyCartnewShoppingCart();console.log(myCart.increment());// 1// console.log(myCart.#count); // ❌ 报错语法级别禁止外部访问为什么更优雅以前用闭包你需要写一个外层函数包裹再返回对象代码嵌套深。现在用 Class结构一目了然而且#count是“硬私有”外部连看都看不到比闭包更安全。方案二ES Module 模块系统适合工具类 ️如果你的计数器是一个独立的工具文件比如cart.js你可以利用 ES 模块天然的作用域隔离。// cart.js// 没有 export 的变量天然就是私有的letcount0;exportfunctionincrement(){count;returncount;}exportfunctiongetCount(){returncount;}// main.jsimport{increment,getCount}from./cart.js;console.log(increment());// 1// console.log(count); // ❌ 报错外部根本拿不到 count为什么更优雅你不需要写任何包裹函数只要不写export这个变量就对外界隐身了。这是目前前端工程化如 Vue、React 项目中最常用的私有化方式。方案三WeakMap 模拟私有适合老项目兼容 ️如果你的项目需要兼容非常老的浏览器不能用#可以用WeakMap结合原型来实现。constprivateDatanewWeakMap();classShoppingCart{constructor(){// 把私有数据存在 WeakMap 里以当前实例为 keyprivateData.set(this,{count:0});}increment(){constdataprivateData.get(this);data.count;returndata.count;}}为什么提这个纯闭包每次创建实例都会生成新的函数内存开销大。而WeakMap方案把方法放在原型prototype上复用只把数据隔离在老项目中能兼顾复用与私有。方案该怎么选写组件、写业务对象无脑选Class # 私有字段。语义最明确IDE 提示最友好别人一看就知道这是私有变量。写工具函数、独立模块用ES Module不export。代码最干净符合现代前端工程规范。什么时候还用闭包当你需要动态生成一些带有独立状态的函数时比如我们前面讲的防抖函数debounce每次调用都需要一个全新的timer闭包依然是无可替代的神器