自定义事件:让代码之间也能“悄悄对话”

张开发
2026/4/4 8:31:20 15 分钟阅读
自定义事件:让代码之间也能“悄悄对话”
你有没有想过除了浏览器自带的click、mouseover这些事件我们能不能自己创造事件比如“用户通关了”、“购物车满了”、“天气变热了”今天我们就来学学自定义事件让你能在代码的各个角落“放信号弹”让其他模块“听到”后自动响应。前言想象一下你是个指挥官手下有侦察兵、炮兵、步兵。侦察兵发现敌情后不能直接喊“开炮”否则太乱。他需要一种机制——比如举起红旗——让炮兵看到红旗就开火步兵看到红旗就隐蔽。这个“红旗”就是自定义事件。在JS里自定义事件就是让不同的模块通过“信号”通信而不需要互相引用、直接调用。这样代码更松耦合更好维护。一、自定义事件的三种姿势1. 最简单的new Event()用new Event()创建一个基础事件对象然后用dispatchEvent()触发。// 创建一个自定义事件constmyEventnewEvent(myCustomEvent,{bubbles:true,// 是否冒泡cancelable:true// 是否可取消默认行为});// 监听这个事件document.addEventListener(myCustomEvent,(e){console.log(收到了自定义事件);});// 触发事件document.dispatchEvent(myEvent);这个事件可以冒泡可以取消但它不能携带额外数据。2. 能带数据的new CustomEvent()CustomEvent是Event的升级版它多了一个detail属性可以用来传递数据。constloginEventnewCustomEvent(userLogin,{detail:{username:张三,timestamp:Date.now()}});window.addEventListener(userLogin,(e){console.log(${e.detail.username}登录了时间是${e.detail.timestamp});});window.dispatchEvent(loginEvent);这个就厉害了你想传啥就传啥——对象、数组、甚至函数都行。3. 最古老的document.createEvent()这是老式写法兼容IE等古董浏览器但现在已经很少用了。了解一下就行constoldEventdocument.createEvent(Event);oldEvent.initEvent(myEvent,true,true);document.dispatchEvent(oldEvent);日常开发用CustomEvent就完事了。二、实战用自定义事件做模块通信假设我们有一个游戏玩家通关后需要同时做三件事播放胜利音乐、显示通关动画、记录分数。如果用传统方式通关模块得分别调用音乐模块、动画模块、分数模块的方法耦合度太高。用自定义事件就优雅多了// 通关模块functionpassLevel(){console.log(玩家通关了);// 发信号consteventnewCustomEvent(levelPassed,{detail:{level:3,score:1000}});window.dispatchEvent(event);}// 音乐模块window.addEventListener(levelPassed,(e){console.log(播放第${e.detail.level}关胜利音乐);});// 动画模块window.addEventListener(levelPassed,(e){console.log(播放通关烟花动画);});// 分数模块window.addEventListener(levelPassed,(e){console.log(记录分数${e.detail.score});});// 通关passLevel();每个模块只关心自己该做的事互不干扰。要加新功能再加一个监听器就行完全不用改动原有代码。三、事件冒泡自定义事件也能往上“飘”如果自定义事件是在某个元素上触发的它可以像原生事件一样冒泡被父元素捕获。dividparentbuttonidchild点我/button/divconstchilddocument.getElementById(child);constparentdocument.getElementById(parent);parent.addEventListener(customClick,(e){console.log(父元素收到了事件目标,e.target);});child.addEventListener(customClick,(e){console.log(子元素收到了);// 如果不阻止冒泡父元素也会收到});constmyEventnewEvent(customClick,{bubbles:true});child.dispatchEvent(myEvent);注意CustomEvent默认也是可以冒泡的只要bubbles: true。四、与原生事件的微妙差异自定义事件和原生事件在使用上几乎一样但有个本质区别自定义事件不是浏览器自己产生的。这意味着不会触发浏览器的默认行为比如点击链接跳转。不会影响页面的原生交互比如输入框获得焦点。在DevTools的Event Listeners面板里可能不会显示但现代浏览器基本都支持显示。五、应用场景什么时候该用自定义事件1. 插件/组件通信比如你写了一个轮播图插件它内部可以派发slideChange事件让外面知道当前切换到第几张了。// 插件内部this.dispatchEvent(newCustomEvent(slideChange,{detail:{index:currentIndex}}));// 使用者carousel.addEventListener(slideChange,(e){console.log(现在展示第,e.detail.index,张);});2. 松耦合架构在大型应用中不同模块之间最好不要互相知道对方的存在。自定义事件就是天然的“消息总线”。// 购物车模块functionaddToCart(item){// 添加逻辑...window.dispatchEvent(newCustomEvent(cartUpdated,{detail:{item,count:newCount}}));}// 导航栏模块显示购物车图标上的数字window.addEventListener(cartUpdated,(e){updateCartIcon(e.detail.count);});// 数据分析模块window.addEventListener(cartUpdated,(e){trackEvent(add_to_cart,e.detail.item);});3. 跨组件、跨层级通信在React/Vue等框架里父子组件通信有props和emit但爷孙组件、兄弟组件通信就麻烦些。这时候可以在公共父级或全局对象上派发自定义事件当然也可以用状态管理工具但自定义事件是轻量选择。六、清理事件别忘了“退订”自定义事件也是事件用完了要记得移除监听器否则可能内存泄漏。functionhandler(e){console.log(收到了);}window.addEventListener(myEvent,handler);// 某天不需要了window.removeEventListener(myEvent,handler);如果用的是匿名函数是没法移除的所以最好把监听器函数单独定义。七、实战一个简单的“全局事件总线”我们可以封装一个极简的事件总线方便在任何地方监听和触发consteventBus{on(event,callback){document.addEventListener(event,callback);},off(event,callback){document.removeEventListener(event,callback);},emit(event,data){document.dispatchEvent(newCustomEvent(event,{detail:data}));}};// 使用eventBus.on(userLogin,(e){console.log(登录了,e.detail);});eventBus.emit(userLogin,{name:张三});就这几行你就有了一个全局通信工具。八、总结自定义事件让代码“会说话”创建事件new Event()不带数据或new CustomEvent()带数据触发事件用dispatchEvent()派发监听事件用addEventListener()接收冒泡设置bubbles: true可以让事件向上传播应用场景插件通信、松耦合架构、跨组件通信注意用完后移除监听器避免内存泄漏自定义事件是JS里非常优雅的通信方式它让不同模块之间可以“悄悄对话”而不需要手拉手、硬耦合。掌握了它你的代码会变得更灵活、更容易维护。明天我们将进入MutationObserver的世界——一个能监听DOM变化的神器当你想知道页面上的某个元素什么时候被添加、删除、修改属性时它就是你最好的“卧底”。如果你觉得今天的自定义事件够“传神”点个赞让更多人看到。我们明天见

更多文章