小白前端别懵圈:搞懂事件表,再也不怕点击没反应了!
- 小白前端别懵圈:搞懂事件表,再也不怕点击没反应了!
- 为啥你点按钮像打空气?
- 事件表到底藏哪儿了?
- 浏览器背后的小本本:事件表是啥玩意儿
- 这东西好用归好用,但坑也不少
- 内存泄漏大户:忘了 removeEventListener,事件表就成垃圾场
- 重复绑定?事件表可不会自动去重
- 移动端 touch 和 click 混着绑?事件表直接给你表演“双倍快乐”
- 真实项目里怎么玩转事件表
- 动态列表里的按钮怎么绑事件才不崩?
- 框架时代(React/Vue)还用管事件表吗?
- 监听器不生效?先别砸键盘
- 是不是作用域搞错了?this 指向翻车现场
- 是不是元素还没渲染完你就急着绑?
- 是不是用了箭头函数结果丢了 event 对象?
- 几个骚操作让你写得更稳
- 统一管理事件监听器,自己建个“伪事件表”做登记注销
- 开发阶段加个 console.log 打印当前绑了多少监听,防爆炸
- 用 AbortController 一键清除多个监听,告别 removeEventListener 地狱
- 下次再遇到“点了没反应”,别只会刷新页面了
小白前端别懵圈:搞懂事件表,再也不怕点击没反应了!
为啥你点按钮像打空气?
先别急着骂浏览器,也别摔鼠标。
你刚写完一个按钮,信心满满地刷新页面,手指一点——啪!空气。
控制台干净得像你刚擦过的 MacBook 屏幕,没有任何报错。
你怀疑人生:我写的addEventListener被狗吃了?
别慌,这不是灵异事件,也不是你电脑被产品经理远程下了降头。
真相只有一个:事件表把你耍了。
事件表到底藏哪儿了?
先给你打个预防针:
事件表不是一张 Excel,也不是window.eventTable,更不是 DOM 节点上的属性。
它藏在浏览器的 C++ 底层,属于“内核大佬的私人日记”。
JS 层面你看不见、摸不着,但它比你的 ex 还记仇。
当你写下:
btn.addEventListener('click',()=>alert('hello'));浏览器内核立刻在日记里记一笔:
“btn 元素,click 事件,回调地址 0x7f3e4a2b,捕获 false,一次 false,优先级 0。”
下次你点按钮,内核翻日记:“哦,这哥们有登记”,于是把回调扔进事件队列,等主线程空了再执行。
整个流程像极了你去网红店排队:先拿号(注册),再等叫号(队列),最后吃上了(执行)。
浏览器背后的小本本:事件表是啥玩意儿
为了让你感受更直观,我直接给你看一段“伪源码”——
注意,是伪的,别真去 Chromium 里搜,搜到你秃头也搜不到。
// 伪代码:Blink 内核里的简化事件表classEventTarget{// key: 事件类型字符串 "click"// value: 监听器数组,按绑定顺序排队HashMap<String,Vector<Listener>>listenerMap;};structListener{void*callback;// 函数指针booluseCapture;// 是否捕获阶段boolonce;// 是否只执行一次AbortSignal*signal;// 可选的 AbortController 信号};看到没?本质就是一个哈希表 + 数组。
你每addEventListener一次,就往数组里push一个结构体。
重复绑定?数组就膨胀,浏览器不会帮你去重。
所以点一次触发三次,真不是你手抖,是你代码手抖。
这东西好用归好用,但坑也不少
内存泄漏大户:忘了 removeEventListener,事件表就成垃圾场
很多小伙伴写完组件直接xxx = null,以为 GC 会扫地。
但内核的小本本上还有引用,闭包里的变量一样被按住,内存就 leak 了。
像极了你分手后还留着对方的微信,结果每晚翻朋友圈emo。
正确姿势:
统一注销,别偷懒。
functionsetup(){consthandler=()=>console.log('click');btn.addEventListener('click',handler);// 返回清理函数,爱啥时候调就啥时候调return()=>btn.removeEventListener('click',handler);}constcleanup=setup();// 组件销毁时cleanup();// 干净利落,不留痕迹重复绑定?事件表可不会自动去重
有人图方便,在useEffect或mounted里重复执行:
// React 例子useEffect(()=>{window.addEventListener('resize',handleResize);},[handleResize]);// 依赖一变就重新绑一次结果 resize 的时候触发 N 次,风扇转得比直升机还响。
解决思路:先清再绑,或者只绑一次。
useEffect(()=>{window.addEventListener('resize',handleResize);return()=>window.removeEventListener('resize',handleResize);},[]);// 空依赖,只绑一次移动端 touch 和 click 混着绑?事件表直接给你表演“双倍快乐”
移动浏览器为了兼容,会在touchend后模拟一次 click,俗称“幽灵点击”。
如果你同时绑了touchstart和click,就可能触发两次。
解决办法:
- 只用
touchstart+preventDefault把模拟 click 吃掉; - 或者直接用
pointerup,一劳永逸。
btn.addEventListener('touchstart',e=>{e.preventDefault();// 吃掉幽灵点击doSomething();});真实项目里怎么玩转事件表
动态列表里的按钮怎么绑事件才不崩?
场景:你渲染了 1000 行表格,每行都有“删除”按钮。
傻办法:循环 1000 次addEventListener,手机直接变暖手宝。
聪明办法:事件委托,把事件表压缩成一条记录。
// 只绑一次,挂在父级table.addEventListener('click',e=>{// 利用 dataset 识别目标if(e.target.dataset.action==='delete'){constid=e.target.dataset.id;deleteRow(id);}});优点:
- 无论新增还是删除行,都不需要重新绑;
- 事件表只有一条记录,内存稳;
- 代码少,产品经理挑不出毛病。
框架时代(React/Vue)还用管事件表吗?
React 的onClick={foo}看起来人畜无害,其实底层还是addEventListener,只是 React 帮你做了合成事件池(SyntheticEvent)。
Vue 的@click也一样,最终都会登记到浏览器的小本本。
框架不管注销,你自己得管。
// React 函数组件,防止内存泄漏 useEffect(() => { const handler = e => { if (e.key === 'Escape') closeModal(); }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); }, []);监听器不生效?先别砸键盘
是不是作用域搞错了?this 指向翻车现场
classCounter{count=0;constructor(){btn.addEventListener('click',this.add);// 完蛋,this 丢了}add(){this.count++;// this 指向 button,不是 Counter}}解决:
btn.addEventListener('click',this.add.bind(this));// 或者箭头函数btn.addEventListener('click',()=>this.add());是不是元素还没渲染完你就急着绑?
// 错误示范:元素都没出生就绑document.querySelector('#future-btn').addEventListener('click',handler);正确姿势:等 DOM 就位。
window.addEventListener('DOMContentLoaded',()=>{document.querySelector('#future-btn').addEventListener('click',handler);});是不是用了箭头函数结果丢了 event 对象?
btn.addEventListener('click',event=>{console.log(event.target);// 一切正常});如果你写成:
btn.addEventListener('click',()=>{console.log(event);// 卧槽,用的是外层 window.event,IE 时代遗留毛病});箭头函数没有自己的arguments,也没有event参数,千万别混。
几个骚操作让你写得更稳
统一管理事件监听器,自己建个“伪事件表”做登记注销
classEventBook{#store=newMap();// 伪事件表on(target,type,handler,options){target.addEventListener(type,handler,options);constkey=`${target}-${type}-${handler}`;this.#store.set(key,{target,type,handler});}off(target,type,handler){constkey=`${target}-${type}-${handler}`;if(this.#store.has(key)){const{target:t,type:tp,handler:h}=this.#store.get(key);t.removeEventListener(tp,h);this.#store.delete(key);}}offAll(){this.#store.forEach(({target,type,handler})=>{target.removeEventListener(type,handler);});this.#store.clear();}}// 使用constbook=newEventBook();book.on(btn,'click',handler);// 一键清空book.offAll();开发阶段加个 console.log 打印当前绑了多少监听,防爆炸
letcounter=0;constoriginal=EventTarget.prototype.addEventListener;EventTarget.prototype.addEventListener=function(type,handler,options){console.log(`[Event] +1${type}on${this}`,++counter);returnoriginal.call(this,type,handler,options);};上线前记得删掉,不然绩效被扣别找我。
用 AbortController 一键清除多个监听,告别 removeEventListener 地狱
constctrl=newAbortController();const{signal}=ctrl;btn1.addEventListener('click',handler1,{signal});btn2.addEventListener('click',handler2,{signal});window.addEventListener('resize',handler3,{signal});// 一键全清ctrl.abort();浏览器支持:Chrome 88+、Firefox 86+、Safari 14+。
老项目要兼容 IE?那还是老老实实removeEventListener吧,别把老板逼疯。
下次再遇到“点了没反应”,别只会刷新页面了
刷新页面是前端工程师的“重启试试”,治标不治本。
先打开控制台,看看元素选对没;
再console.log确认回调进没进;
最后瞄一眼事件表——哦不,是瞄一眼你的代码,是不是重复绑、忘了清、this 飘了、元素还没生出来你就绑。
和事件表谈一场坦白局,你会发现:
- 内存泄漏少了,风扇声轻了;
- 重复绑定没了,bug 收敛了;
- 幽灵点击防住了,移动端不抖了;
- 绩效上去了,女朋友也……还是不在,但至少代码稳了。
唠到这儿,五千字应该有了。如果还不过瘾,去把removeEventListener写 100 遍,感受一下事件表的温柔。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐: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等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!