news 2026/1/8 21:21:38

闭包不难懂:前端开发者必须掌握的JavaScript核心技巧(附实战场

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
闭包不难懂:前端开发者必须掌握的JavaScript核心技巧(附实战场


闭包不难懂:前端开发者必须掌握的JavaScript核心技巧(附实战场

  • 闭包不难懂:前端开发者必须掌握的JavaScript核心技巧(附实战场景)
    • 引言:从一个让人困惑的小例子说起
    • 闭包到底是什么?别被术语吓到
    • 作用域链的温柔陷阱:变量为何“赖着不走”
    • 闭包的三种经典写法,你用过几种?
      • 1. 函数工厂——批量生产“配置怪兽”
      • 2. 立即执行函数(IIFE)——“用完即焚”的忍者
      • 3. 回调延迟——“先订票,后上车”
    • 为什么闭包是模块化和封装的秘密武器?
    • 闭包在事件处理、定时器和回调中的妙用
      • 1. 给循环中的按钮发“身份证”
      • 2. 节流(throttle)——“地铁安检,10 秒过一次”
      • 3. 防抖(debounce)——“输入结束 500ms 后才发请求”
    • 性能代价与内存泄漏:闭包不是免费午餐
    • 排查闭包问题的三大信号:变量异常、内存飙升、函数行为诡异
    • 开发中巧用闭包提升代码优雅度的五个小技巧
    • 当闭包遇上箭头函数:小心 this 和词法作用域的双重夹击
    • 闭包模拟私有变量:让数据真正“藏”起来
    • 别再死记定义!用生活场景理解闭包的运行机制
    • 闭包调试实战:Chrome DevTools 里怎么揪出那个“赖着不走”的变量?
    • 闭包反模式警示:这些写法看似聪明实则埋雷
    • 高级玩法预览:闭包如何助力函数式编程和高阶组件?
      • 1. 函数式里的 `compose` & `pipe`
      • 2. React 高阶组件(HOC)——“组件外套”
    • 尾声:把闭包变成肌肉记忆

闭包不难懂:前端开发者必须掌握的JavaScript核心技巧(附实战场景)

引言:从一个让人困惑的小例子说起

先别急着翻文档,咱们从一个“看起来人畜无害”的面试题开始:

for(vari=0;i<5;i++){setTimeout(()=>console.log(i),i*1000);}

猜猜会打印什么?
5 个 5,整整齐齐,一秒一个,像报时一样精准。
第一次撞见这行代码的我,差点把键盘掀了——i不是应该 0、1、2、3、4 吗?怎么集体叛变?

别急,这篇文章就把“叛变”背后的主谋——闭包——揪出来。咱们不背定义,先破案,再谈恋爱,最后让它给你打工。


闭包到底是什么?别被术语吓到

先上“官方”黑话:

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

翻译成人话:函数 A 里 return 了函数 B,B 把 A 的变量“私藏”了,A 死后,变量还活着
就像你把老爸的信用卡绑到了自己微信里,老爸人走了,卡还能刷——道德上谴责,技术上真香。

看代码:

functiondad(){constmoney=10000;// 老爸的私房钱returnfunctionson(){// 儿子把卡揣走了console.log(`刷老爸的卡,余额:${money}`);};}constuseDadCard=dad();// dad 执行完,理论上 money 应该被回收useDadCard();// 但儿子还能刷,闭包让 money 活着

变量money本该随dad()的调用结束而销毁,可son把它“闭”在了自己的背包里,这就是闭包——背包里装着别人的遗产,还不用交遗产税


作用域链的温柔陷阱:变量为何“赖着不走”

JS 的变量查找像“套娃”:
当前函数没有?问爸爸;爸爸没有?问爷爷;一直到全局。
闭包就是把“爷爷”装进了“孙子”的口袋,爷爷明明已经退休了,还得随叫随到。

画个图太抽象,直接上代码:

functiongrandpa(){constname='张三';returnfunctionfather(){constage=60;returnfunctiongrandson(){console.log(`${name}的孙子,今年${age}`);};};}constgrandson=grandpa()();// 两层调用grandson();// 张三 的孙子,今年 60

grandsongrandpafather的变量都“绑架”了,谁 return 我,我就带走谁的作用域
所以变量不是“赖着不走”,是被合法劫持


闭包的三种经典写法,你用过几种?

1. 函数工厂——批量生产“配置怪兽”

functioncreateMultiplier(factor){// factor 被闭包returnfunction(n){returnn*factor;};}constdouble=createMultiplier(2);consttriple=createMultiplier(3);console.log(double(5));// 10console.log(triple(5));// 15

工厂模式,一次配置,终身享用。factor就像咖啡机里的豆仓,换豆不换机。

2. 立即执行函数(IIFE)——“用完即焚”的忍者

constcounter=(function(){letcount=0;// 私有变量return{inc(){count++;},get(){returncount;}};})();counter.inc();counter.inc();console.log(counter.get());// 2console.log(count);// ReferenceError: count is not defined

IIFE 把变量“锁”在黑洞里,只暴露 API,不暴露数据,Node 里早期模块就是靠它模拟私有。

3. 回调延迟——“先订票,后上车”

functiondelayPrint(msg,time){setTimeout(function(){console.log(msg);// msg 被闭包},time);}delayPrint('三秒后见',3000);

setTimeout的回调把msg揣走了,定时器到时再掏出来,这是前端最最常见的闭包场景,没有之一。


为什么闭包是模块化和封装的秘密武器?

ES6 之前,JS 没有private,想藏变量?只能靠闭包。
Node 的 CommonJS 模块,编译后大致长这样:

(function(exports,require,module,__filename,__dirname){letsecret='top-secret-key';functiongetSecret(){returnsecret;}module.exports={getSecret};});

每个模块都被一个外层匿名函数包裹,局部变量secret外部永远拿不到。
闭包=天然沙箱,不用写private,也能把变量“关小黑屋”。


闭包在事件处理、定时器和回调中的妙用

1. 给循环中的按钮发“身份证”

<button>0</button><button>1</button><button>2</button><script>constbtns=document.querySelectorAll('button');// 错误示范:所有按钮弹出 3// for (var i = 0; i < btns.length; i++) {// btns[i].onclick = () => alert(i);// }// 正确姿势:闭包给每个按钮拍照for(vari=0;i<btns.length;i++){(function(idx){btns[idx].onclick=()=>alert(idx);})(i);}</script>

IIFE 为每一次循环都创建新作用域,idx被“定格”在照片里,点谁弹谁,不再集体叛变。

2. 节流(throttle)——“地铁安检,10 秒过一次”

functionthrottle(fn,wait){lettimer=null;returnfunction(...args){if(timer)return;// 还在冷却,直接 returntimer=setTimeout(()=>{fn.apply(this,args);timer=null;},wait);};}window.onresize=throttle(()=>console.log('resize'),1000);

timer被闭包,多次触发只认第一次,性能省下的都是真金白银。

3. 防抖(debounce)——“输入结束 500ms 后才发请求”

functiondebounce(fn,delay){lett=null;returnfunction(...args){clearTimeout(t);t=setTimeout(()=>fn.apply(this,args),delay);};}constsearch=debounce(q=>fetch(`/search?q=${q}`),500);input.oninput=e=>search(e.target.value);

闭包保存“上一次”的定时器 ID,用户敲得越快,重置得越频繁,直到 Ta 停下来喘口气。


性能代价与内存泄漏:闭包不是免费午餐

闭包=变量常驻内存
用得好是性能飞升,用不好就是内存吸血鬼
Chrome DevTools 的 Memory 面板里,如果看到Detached HTMLDivElement数量飙升,十有八九是闭包没松绑:

// 雷区:循环里绑定闭包,却忘记清理for(leti=0;i<100;i++){constdiv=document.createElement('div');div.onclick=function(){// div 被闭包引用console.log(i);};document.body.appendChild(div);// 后期移除 div 时,onclick 没置 null,导致 div 无法回收}

正确姿势:移除节点时,顺手把事件解绑或置空:

div.onclick=null;// 手动松绑div.remove();

排查闭包问题的三大信号:变量异常、内存飙升、函数行为诡异

  1. 变量值跟预期对不上
    八成是共享同一个闭包变量,而不是“每人一份”。

  2. 快照里 Detached Nodes 暴涨
    闭包把 DOM 给“绑架”了,节点从树上摘掉,却还在内存里飘着。

  3. 函数“记忆”了旧数据
    比如 React 的useEffect依赖没写全,闭包捕获了过期的state,页面怎么点都不更新。


开发中巧用闭包提升代码优雅度的五个小技巧

  1. 一次挂载,终身受用
    把配置闭包,避免层层传递:

    constapi=(function(base){returnfunction(endpoint){return`${base}/${endpoint}`;};})('https://api.my.com');api('user');// https://api.my.com/user
  2. 私有变量模拟
    不想上 TypeScript 的private?闭包来凑:

    functioncreateStack(){constitems=[];// 外部休想碰return{push(x){items.push(x);},pop(){returnitems.pop();},size(){returnitems.length;}};}
  3. 缓存昂贵计算
    memorize 函数一行搞定:

    constmemo=fn=>{constcache=newMap();returnfunction(arg){if(cache.has(arg))returncache.get(arg);constresult=fn(arg);cache.set(arg,result);returnresult;};};constfib=memo(n=>n<2?n:fib(n-1)+fib(n-2));
  4. 柯里化
    把多参函数拆成单参,靠闭包保存前面的参数:

    constadd=a=>b=>a+b;constadd5=add(5);console.log(add5(3));// 8
  5. 迭代器
    用闭包保存当前索引,每次调用next()吐一个值:

    functioncreateIterator(arr){leti=0;returnfunctionnext(){returni<arr.length?{value:arr[i++],done:false}:{done:true};};}

当闭包遇上箭头函数:小心 this 和词法作用域的双重夹击

箭头函数没有自己的this外层 this 被一起闭包进来,这在 React 里尤其酸爽:

classCounterextendsReact.Component{state={count:0};handleClick=()=>{// 这里的 this 就是组件实例,因为箭头函数把外部 this 闭包了this.setState({count:this.state.count+1});};render(){return<button onClick={this.handleClick}>+</button>;}}

如果把handleClick写成普通函数,就得手动bind(this)或者用@autobind装饰器,箭头函数+闭包=省 bind 神器


闭包模拟私有变量:让数据真正“藏”起来

ES2022 才引入#私有字段,之前全靠闭包:

functioncreateBankCard(password){let_balance=0;// 真正的私有return{deposit(pwd,amount){if(pwd!==password)thrownewError('密码错误');_balance+=amount;},withdraw(pwd,amount){if(pwd!==password)thrownewError('密码错误');if(amount>_balance)thrownewError('余额不足');_balance-=amount;},getBalance(pwd){if(pwd!==password)thrownewError('密码错误');return_balance;}};}constcard=createBankCard('123456');card.deposit('123456',1000);console.log(card.getBalance('123456'));// 1000console.log(card._balance);// undefined,外部根本拿不到

_balance被闭包保护,暴力破解都没门,比Symbol那种“假装私有”靠谱多了。


别再死记定义!用生活场景理解闭包的运行机制

把闭包想成外卖员背着的保温箱

  • 店家(外部函数)把饭(变量)做好,放进保温箱(return 的函数)。
  • 店家关门(外部函数执行完毕),饭却还在保温箱里。
  • 你拿到外卖(调用内部函数),打开一看,饭还是热的(变量还在)。

保温箱=闭包,饭=变量,热=持久化
下次再有人问你闭包,就告诉他:“外卖员的小箱子,懂了吧?”


闭包调试实战:Chrome DevTools 里怎么揪出那个“赖着不走”的变量?

  1. 打开 DevTools → Memory → Take snapshot。
  2. 过滤Closure关键字,能看到哪些对象被闭包引用。
  3. 点击具体条目,右侧会显示Scope → Closure里的变量名和值。
  4. 如果发现意料之外的巨型数组、DOM 节点,恭喜你,找到内存元凶。
  5. 回到代码,手动解除引用onclick = nullcache.clear()timer = null

真实案例:某后台系统跑 3 天页面卡死,快照里一个闭包抱着 200M 的 JSON 配置不放,把配置拆成按需加载后,内存从 1.2G 降到 200M,老板直接发了 500 块奖金。


闭包反模式警示:这些写法看似聪明实则埋雷

  1. 闭包里塞超大对象
    以为缓存一时爽,用户不刷新页面就永驻内存,10 分钟就能把 Tab 拖垮。

  2. 循环里写addEventListener却不清理
    单页应用里路由切换,旧 DOM 被移除但事件闭包还在, Detached Nodes 越积越多。

  3. 把闭包当“全局变量”用
    为了省传参,什么都往闭包塞,逻辑分散,调试想死,接手的人只想给你寄刀片。


高级玩法预览:闭包如何助力函数式编程和高阶组件?

1. 函数式里的compose&pipe

constcompose=(...fns)=>x=>fns.reduceRight((v,f)=>f(v),x);constpipe=(...fns)=>x=>fns.reduce((v,f)=>f(v),x);constadd=x=>x+1;constmul=x=>x*2;constaddThenMul=compose(mul,add);// 先 add 再 mulconsole.log(addThenMul(5));// 12

compose返回的函数把fns数组闭包在内,调用时再依次执行,行云流水,毫无副作用

2. React 高阶组件(HOC)——“组件外套”

functionwithLogger(WrappedComponent){returnfunction(props){useEffect(()=>{console.log('组件挂载',WrappedComponent.name);},[]);return<WrappedComponent{...props}/>;};}

withLogger返回的新组件把WrappedComponent闭包在内部,每次复用都能拿到原始组件,AOP 切面包办日志、权限、埋点,不要太爽。


尾声:把闭包变成肌肉记忆

闭包就像骑自行车——看理论永远学不会,摔两次坑自然就记住了
下次写循环、绑事件、做缓存之前,先问自己三句话

  1. 变量会不会被“共享”?
  2. 函数结束后还要不要“带数据”?
  3. 用完之后有没有“松绑”?

把这三问刻进 DNA,闭包就从“面试杀手”变成“开发利器”
愿你写的每一行代码,都能让变量在正确的时间、正确的地方,优雅地活着,也能优雅地离开

欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!


专栏系列(点击解锁)学习路线(点击解锁)知识定位
《微信小程序相关博客》持续更新中~结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等
《AIGC相关博客》持续更新中~AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结
《HTML网站开发相关》《前端基础入门三大核心之html相关博客》前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识
《前端基础入门三大核心之JS相关博客》前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。
通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心
《前端基础入门三大核心之CSS相关博客》介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页
《canvas绘图相关博客》Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化
《Vue实战相关博客》持续更新中~详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅
《python相关博客》持续更新中~Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具
《sql数据库相关博客》持续更新中~SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能
《算法系列相关博客》持续更新中~算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维
《IT信息技术相关博客》持续更新中~作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识
《信息化人员基础技能知识相关博客》无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方
《信息化技能面试宝典相关博客》涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面
《前端开发习惯与小技巧相关博客》持续更新中~罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等
《photoshop相关博客》持续更新中~基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结
日常开发&办公&生产【实用工具】分享相关博客》持续更新中~分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具

吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/6 15:50:18

作为其整体增长战略的一部分,SBC Medical对美国领先医疗美容平台OrangeTwist进行战略投资并与之达成合作联盟,以加速全球扩张进程

为医疗企业及其诊所提供综合咨询与管理服务的全球服务商SBC Medical Group Holdings Incorporated (Nasdaq: SBC)&#xff08;以下简称“SBC Medical”或“公司”&#xff09;今日宣布&#xff0c;已联合长期机构股东Hildred Capital和Athyrium Capital&#xff0c;完成对美国领…

作者头像 李华
网站建设 2026/1/6 15:44:15

本地部署智能家居系统 OpenHAB 并实现外部访问( Windows 版本)

OpenHAB&#xff08; Open Home Automation Bus &#xff09;是一款开源的智能家居系统&#xff0c;它允许用户连接并控制各种智能设备&#xff0c;无论这些设备是来自不同制造商还是基于不同的技术标准&#xff0c;适合那些希望将家中的智能设备统一管理&#xff0c;并希望通过…

作者头像 李华
网站建设 2026/1/6 15:39:54

Z-Image-Base模型分布式训练细节披露:用了多少节点?

Z-Image-Base模型分布式训练细节披露&#xff1a;用了多少节点&#xff1f; 在生成式AI的浪潮中&#xff0c;文生图大模型正从科研实验室走向千行百业。然而&#xff0c;一个现实问题始终横亘在落地路径上&#xff1a;如何在保障图像质量的同时&#xff0c;兼顾推理效率与部署成…

作者头像 李华