【超详细】前端必备:从0到1吃透JavaScript闭包,附真实项目避坑指南

news2026/4/1 8:30:27
文章目录第一章 从“变量生命周期”开始重新理解作用域链1.1 一个让新手困惑的面试题循环中的var与let1.2 作用域链的形成函数定义位置决定了一切第二章 闭包的工程价值从封装到模块化2.1 数据私有化用闭包实现真正的“私有变量”2.2 实战案例实现一个带缓存的API请求函数第三章 深入内存模型闭包的性能陷阱与优化3.1 最常见的内存泄露DOM节点引用残留3.2 性能陷阱循环中创建大量闭包的开销第四章 现代JavaScript中的闭包应用React Hooks与设计模式4.1 深入React HooksuseState和useEffect背后的闭包原理4.2 设计模式落地用闭包实现简易的状态管理库第五章 生产环境必知闭包调试与代码审查要点5.1 Chrome DevTools 中查看闭包内容5.2 代码审查中的闭包反模式清单第一章 从“变量生命周期”开始重新理解作用域链1.1 一个让新手困惑的面试题循环中的var与let几乎所有前端面试都会考这道题因为它精准戳中了闭包的核心痛点。// 面试常见陷阱for(vari0;i3;i){setTimeout(function(){console.log(i);},100);}// 输出3 3 3而不是预期的 0 1 2初学者往往无法理解为什么输出全是3。根本原因在于var声明的变量i存在函数作用域而非块级作用域循环结束后i已经变成了3。而setTimeout中的回调函数在循环结束后才执行它们访问的是同一个i。修复方案有两种使用let替代varlet每次迭代会创建新的绑定使用闭包为每次迭代保存当前i的值// 方案二利用闭包保存状态for(vari0;i3;i){(function(j){setTimeout(function(){console.log(j);},100);})(i);}// 输出0 1 2这里的立即执行函数IIFE创建了一个独立的作用域参数j保存了每次循环时i的当前值而内部setTimeout的回调通过闭包机制“记住”了这个j。1.2 作用域链的形成函数定义位置决定了一切闭包的本质是函数可以记住并访问其词法作用域即使该函数在其词法作用域之外执行。functionouter(){letname张三;functioninner(){console.log(name);// inner 可以访问 outer 中的变量}returninner;}constclosureFuncouter();closureFunc();// 输出张三当outer执行完毕后按理说name应该被垃圾回收。但由于inner被返回并赋值给closureFuncinner仍然持有对name的引用所以name存活了下来。这就是闭包的核心机制内部函数持有外部函数变量的引用阻止其被回收。第二章 闭包的工程价值从封装到模块化2.1 数据私有化用闭包实现真正的“私有变量”JavaScript 在 ES2022 之前没有真正的私有字段语法#前缀闭包是实现数据私有化的经典手段。functioncreateCounter(){letcount0;// 这个变量对外部完全不可见return{increment:function(){count;returncount;},decrement:function(){count--;returncount;},getCount:function(){returncount;}};}constcountercreateCounter();console.log(counter.count);// undefined无法直接访问console.log(counter.increment());// 1console.log(counter.increment());// 2console.log(counter.getCount());// 2这种模式在真实项目中极其常见比如状态管理、表单验证器、防抖节流函数等。count变量被安全地封装在createCounter的作用域内外部只能通过暴露的接口进行操作避免了全局污染和意外修改。2.2 实战案例实现一个带缓存的API请求函数在业务开发中我们经常需要缓存接口返回结果避免重复请求。闭包是实现缓存函数的绝佳选择。functioncreateApiCache(ttl60000){// ttl 缓存有效期单位毫秒constcachenewMap();returnasyncfunction(url,options{}){constcacheKey${url}_${JSON.stringify(options)};constcachedcache.get(cacheKey);// 缓存命中且未过期if(cachedDate.now()-cached.timestampttl){console.log(缓存命中${url});returncached.data;}console.log(发起真实请求${url});constresponseawaitfetch(url,options);constdataawaitresponse.json();cache.set(cacheKey,{data:data,timestamp:Date.now()});returndata;};}// 创建带缓存的请求函数constcachedFetchcreateApiCache(30000);// 30秒缓存// 第一次调用发起真实请求constuser1awaitcachedFetch(/api/user/123);// 第二次调用命中缓存直接返回constuser2awaitcachedFetch(/api/user/123);这个例子中cache变量被闭包捕获成为请求函数内部的“持久化存储”。所有通过cachedFetch发起的请求共享同一个缓存池但外部无法直接操作缓存保证了数据的可控性。第三章 深入内存模型闭包的性能陷阱与优化3.1 最常见的内存泄露DOM节点引用残留闭包会阻止变量被垃圾回收如果不加注意很容易造成内存泄露。最典型的场景是闭包中持有了已经不用的DOM元素引用。// 危险写法造成内存泄露functionbindEvent(){constlargeDatanewArray(1000000).fill(测试数据);constelementdocument.getElementById(button);element.addEventListener(click,functiononClick(){// 这个闭包持有了 largeData 和 element 的引用console.log(largeData.length);});}bindEvent();// 即使按钮被从DOM中移除largeData 和 element 也无法被回收解决方案是在不需要时主动断开引用或者使用弱引用数据结构WeakMap、WeakSet。// 安全写法使用 WeakMap 避免强引用constelementDataMapnewWeakMap();functionbindEventSafe(){constlargeDatanewArray(1000000).fill(测试数据);constelementdocument.getElementById(button);// 使用 WeakMap 存储数据element 被回收时 largeData 自动释放elementDataMap.set(element,largeData);element.addEventListener(click,functiononClick(){constdataelementDataMap.get(element);console.log(data.length);});}避坑指南在单页应用SPA中如果频繁创建和销毁组件闭包中引用的DOM节点和外部数据必须及时清理。推荐使用WeakMap或手动将闭包引用置为null。3.2 性能陷阱循环中创建大量闭包的开销闭包虽然强大但每个闭包都会占用额外的内存空间存储捕获的变量。在性能敏感的场景下需要权衡使用。// 低性能写法循环中创建大量闭包consthandlers[];for(leti0;i10000;i){handlers.push(function(){// 每个函数都是一个独立的闭包console.log(i);});}// 高性能写法共享函数用参数传递数据functioncreateHandler(index){returnfunction(){console.log(index);};}consthandlersOptimized[];for(leti0;i10000;i){handlersOptimized.push(createHandler(i));}第二种写法虽然本质上还是创建了10000个闭包但通过工厂函数createHandler实现了逻辑复用。如果函数体较大这种写法能减少重复的函数定义内存开销。第四章 现代JavaScript中的闭包应用React Hooks与设计模式4.1 深入React HooksuseState和useEffect背后的闭包原理React Hooks 的底层实现严重依赖闭包。useState返回的set函数能够“记住”对应状态的位置正是因为闭包捕获了当前 fiber 节点的引用。// 一个简化版的 useState 模拟letcurrentComponentnull;lethookIndex0;consthooks[];functionuseState(initialValue){constindexhookIndex;// 闭包捕获当前索引if(!hooks[index]){hooks[index]initialValue;}constsetState(newValue){hooks[index]newValue;// 触发组件重新渲染renderComponent(currentComponent);};hookIndex;return[hooks[index],setState];}闭包陷阱在 React 中useEffect和useCallback的依赖数组如果不正确设置会导致闭包捕获到过期的状态值。functionCounter(){const[count,setCount]useState(0);// 错误写法依赖数组为空闭包捕获的是初始 count 值 0useEffect((){consttimersetInterval((){console.log(count);// 永远输出 0setCount(count1);// 永远变成 1不会继续增加},1000);return()clearInterval(timer);},[]);// 缺少 count 依赖// 正确写法将 count 加入依赖数组useEffect((){consttimersetInterval((){setCount(cc1);// 使用函数式更新避免依赖闭包中的 count},1000);return()clearInterval(timer);},[]);returndiv{count}/div;}最佳实践当状态更新依赖上一个状态时始终使用函数式更新setCount(prev prev 1)这样可以避免依赖数组的闭包陷阱。4.2 设计模式落地用闭包实现简易的状态管理库理解闭包后我们可以自己实现一个类似 Redux 的轻量级状态管理工具。functioncreateStore(reducer,initialState){letstateinitialState;constlisteners[];// 订阅状态变化constsubscribe(listener){listeners.push(listener);// 返回取消订阅函数又是一个闭包return(){constindexlisteners.indexOf(listener);if(index-1)listeners.splice(index,1);};};// 获取当前状态constgetState()state;// 派发 action触发状态更新constdispatch(action){statereducer(state,action);listeners.forEach(listenerlistener());};return{subscribe,getState,dispatch};}// 使用示例constcounterReducer(state0,action){switch(action.type){caseINCREMENT:returnstate1;caseDECREMENT:returnstate-1;default:returnstate;}};conststorecreateStore(counterReducer,0);store.subscribe((){console.log(状态更新了,store.getState());});store.dispatch({type:INCREMENT});// 输出状态更新了1store.dispatch({type:INCREMENT});// 输出状态更新了2这里的state、listeners变量都被内部返回的函数通过闭包捕获外部无法直接修改实现了真正的封装和数据流单向控制。这种模式在生产级的zustand、valtio等状态库中都有广泛应用。第五章 生产环境必知闭包调试与代码审查要点5.1 Chrome DevTools 中查看闭包内容当闭包出现预期外的行为时学会在浏览器开发者工具中调试闭包至关重要。在闭包函数内部设置断点在Scope面板中展开Closure选项查看被捕获的所有变量及其当前值常见问题如果Closure面板中显示的变量数量远超预期说明可能存在不必要的大数据被闭包捕获需要重构代码。5.2 代码审查中的闭包反模式清单在团队代码审查Code Review时重点关注以下几种闭包反模式反模式一在循环中创建函数但未保存状态// 错误所有按钮点击都打印最后一个 ifor(vari0;ibuttons.length;i){buttons[i].onclickfunction(){console.log(i);};}// 正确使用 let 或闭包保存 ifor(leti0;ibuttons.length;i){buttons[i].onclickfunction(){console.log(i);};}反模式二闭包中引用大对象导致内存无法释放// 错误闭包中引用了整个大对象functionprocessData(data){consthugeDatanewArray(1000000).fill(data);returnfunction(){// 只用了 data 的一小部分但 hugeData 整个被保留console.log(data.id);};}// 正确只保留需要的字段functionprocessData(data){const{id}data;// 只提取需要的属性returnfunction(){console.log(id);};}反模式三事件监听器未及时移除// 错误组件销毁时未移除监听器componentDidMount(){window.addEventListener(resize,(){this.handleResize();// 闭包捕获了 this导致组件无法被回收});}// 正确在 componentWillUnmount 中移除componentDidMount(){this.resizeHandler()this.handleResize();window.addEventListener(resize,this.resizeHandler);}componentWillUnmount(){window.removeEventListener(resize,this.resizeHandler);}闭包是 JavaScript 中最重要也最容易被误解的概念之一。从理解作用域链的本质到掌握工程化应用的最佳实践再到避免内存泄露和性能陷阱每一步都需要扎实的理解和充分的实战经验。你在项目中遇到过哪些因闭包引发的诡异 bug或者有独特的闭包应用技巧欢迎在评论区分享交流一起加深对这个核心概念的理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2471368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…