vue中的eventBus會不會產生內存泄漏你知道嗎
eventBus是在vue中經常用來解決跨組件消息傳遞的問題,但對它的使用要特別註意,否則會產生很嚴重的後果。
引入
本文介紹瞭eventBus的實現原理,並介紹它如何在vue中使用,並舉瞭一個具體的例子來說明,如果使用不當,它會造成內存泄漏。
要註意eventBus並不是前端的概念。
由greenrobot [1] 組織貢獻(該組織還貢獻瞭greenDAO),一個Android事件發佈/訂閱輕量級框架,
功能:通過解耦發佈者和訂閱者簡化Android事件傳遞 [2]
EventBus可以代替Android傳統的Intent,Handler,Broadcast或接口函數,在Fragment,Activity,Service線程之間傳遞數據,執行方法。
特點:代碼簡潔,是一種發佈訂閱設計模式(觀察者設計模式)。
內容
- 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:`<div>com1</div>`, created () { this.$eventBus.$on('event1', function f1(d){ conse.log(d, 'com1 listen... event1') }) }, }) Vue.component('com2', { template:`<div>com2</div>`, created () { this.$eventBus.$on('event2', function f2(d) { conse.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:`<div>com1</div>`, created () { conse.log('創建com1') this.$eventBus.$on('event1', function f1(d) { conse.log(d, 'com1 listen... event1') }) } }) Vue.component('com2', { template:`<div>com2</div>`, created () { this.$eventBus.$on('event2', function f2(d) { conse.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:`<div>com1</div>`, 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;
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!