首頁 > 軟體

vue中的eventBus會不會產生記憶體漏失你知道嗎

2022-02-11 13:00:06

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;

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!     


IT145.com E-mail:sddin#qq.com