eventBus是在vue中经常用来解决跨组件消息传递的问题,但对它的使用要特别注意,否则会产生很严重的后果。
引入
本文介绍了eventBus的实现原理,并介绍它如何在vue中使用,并举了一个具体的例子来说明,如果使用不当,它会造成内存泄漏。
要注意eventBus并不是前端的概念。
由greenrobot [1] 组织贡献(该组织还贡献了greenDAO),一个Android事件发布/订阅轻量级框架,
功能:通过解耦发布者和订阅者简化Android事件传递 [2]
EventBus可以代替Android传统的Intent,Handler,Broadcast或接口函数,在Fragment,Activity,Service线程之间传递数据,执行方法。
特点:代码简洁,是一种发布订阅设计模式(观察者设计模式)。
摘自百度百科。https://baike.baidu.com/item/EventBus/20461274?fr=aladdin
内容
eventBus在vue中的实现;
在vue使用eventBus;
使用不当的问题:多次执行回调;内存泄漏;
解决方案:及时调用$off
eventBus在vue中的实现
eventBus是事件总线的意思,它本质上是一个发布订阅者实现,在vue2.X中,vue实例上提供了$on,$emit,$off这三个方法,分别用来添加观察者,发布事件,取消订阅这三个操作。
所以,我们可以直接把一个vue实例挂到Vue的原型上来充当组件相互通信的中介。
Vue.prototype.$eventBus = new Vue()这样一来,所有的Vue组件都可以沿着原型链找到这个$eventBus,从而访问$on, $off,$emit。
它可以帮助我们实现跨组件的通信。

例子:使用eventBus
在根组件中发布事件,在两个子组件中去监听事件。

<div id="app"> <h2>eventBus的基本使用h2> <com1>com1> <com2>com2>div><script> Vue.prototype.$eventBus = new Vue() Vue.component('com1', { template:`com1`, created () { this.$eventBus.$on('event1', function f1(d){ console.log(d, 'com1 listen... event1') }) }, }) Vue.component('com2', { template:`com2`, created () { this.$eventBus.$on('event2', function f2(d) { console.log(d, 'com2 listen... event2') }) } }) var vm = new Vue({ el: '#app', created () { setInterval( () => { const d = Date.now() this.$eventBus.$emit('event1', d) this.$eventBus.$emit('event2', d) }, 3000) } })script>在创建com1组件时,订阅event1事件;在创建com2组件时,订阅event2事件;在创建根组件(vue实例)时,开启定时器:每隔3s发布事件,这样的话,com1和com2就都可以收到事件,并执行对应的回调。
效果如下:

例子:不及时取消订阅
如果不及时取消订阅,则回调函数仍会执行,更严重的是,如果在事件处理回调函数中引用了外部变量形成了闭包,则会导致内存泄漏。
下面的代码说明这个问题。
在根组件(vue实例)中,补充一个数据项showCom1,并配置v-if指令来实现销毁和重建com1组件。
<div id="app"> <h2>不及时取消订阅的问题h2> <button @click="showCom1=!showCom1"> {{showCom1 ? "销毁" : "重建"}}组件1 button> <com1 v-if="showCom1">com1> <com2>com2>div><script> Vue.prototype.$eventBus = new Vue() Vue.component('com1', { template:`com1`, created () { console.log('创建com1') this.$eventBus.$on('event1', function f1(d) { console.log(d, 'com1 listen... event1') }) } }) Vue.component('com2', { template:`com2`, created () { this.$eventBus.$on('event2', function f2(d) { console.log(d, 'com2 listen... event2') }) } }) var vm = new Vue({ el: '#app', data:{ showCom1: true }, created () { setInterval( () => { const d = Date.now() this.$eventBus.$emit('event1', d) this.$eventBus.$emit('event2', d) }, 3000) } })script>先提一个问题:你觉得com1组件被销毁后,它在created中订阅的event1事件还能再收到吗?对应的回调函数还能再执行吗?一般的想法是组件都销毁了,那它订阅的事件肯定也收不到了嘛。
答案是:还能收到。原因很简单:事件订阅这功能是$eventBus对象完成的,与这个com1组件无关。
上面的代码执行的效果,是这样的:
销毁组件1之后,它还能正常收到event1事件,并执行回调;
再次创建组件1后,它会再次订阅event1事件,所以结果是执行两次回调。

下面再来说明内存泄漏的问题,把com1的组件内容改成如下:
Vue.component('com1', { template:`com1`, created () { console.log('创建com1') let m = 1*1024 * 1024 let arr = new Array(m).fill('a') this.$eventBus.$on('event1', function f1(d) { // 注意这里有一个闭包 console.log(d, 'com1 listen... event1', arr[1]) }) }})在回调函数f1中引用函数之外的变量arr,这里有一个闭包。
下面在浏览器的调试工具中的memory添加一个快照,查看结果如下:

然后,点击页面上的“销毁组件1”,再次添加一个快照,你会发现这个空间并没有释放掉。

解释如下:

上面是这个过程的示意图,由于没有及时取消订阅f1,所以arr这个数组并没有释放掉。
解决方案:
在com1的destoryed钩子中,调用$off来取消订阅。
destroyed () { // 取消所有对event1事件的监听 this.$eventBus.$off('event1')}调试结果如下:

可见,com1删除之后,这个数值的空间释放掉了,同时它的事件监听函数也不会再执行了。
其它注意事项
$off的格式:
$off() 会取消所有的事件订阅;
$off('事件名') 会取消指定事件名的;
$off('事件名', 回调) 会取消指定事件名的,指定回调
父子组件的created和mounted的区别, 按执行顺序:
父组件的created 先于子组件的created
父组件的mounted先于子组件的mounted
所以,到底在哪个钩子中订阅,在哪个钩子中发布,要根据情况来定。
小结
eventBus是一个名词,并非前端独有;
new Vue() 得到的实例上已经实现了发布订阅模式,可以直接做eventBus使用;
使用eventBus要及时调用$off;
版权信息:凡人进阶。转载请标明出处。如果对您有帮助,您可以:
转发他人。
小额打赏。
关注本号。
文章详细介绍了 Vue 中 eventBus 的工作原理和使用方法,包括如何在组件间进行通信,以及如果不正确使用(如未及时取消订阅)可能导致的内存泄漏问题。通过示例代码展示了在组件销毁后仍然能接收到事件的情况,强调了解决内存泄漏的必要性——在组件销毁时调用 $off 方法取消订阅。此外,文章还提醒了读者在创建和销毁组件时订阅和发布事件的时机选择,以及 created 和 mounted 钩子的区别。

被折叠的 条评论
为什么被折叠?



