vue中的eventBus會產生內存洩漏嗎

2021-03-02 凡人進階

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


內容

在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){
         console.log(d, 'com1 listen... event1')
      })
    },
  })
   Vue.component('com2', {
     template:`<div>com2</div>`,
     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:`<div>com1</div>`,
     created () {
       console.log('創建com1')
       this.$eventBus.$on('event1', function f1(d) {
         console.log(d, 'com1 listen... event1')
      })
    }
  })
   Vue.component('com2', {
     template:`<div>com2</div>`,
     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組件無關。


上面的代碼執行的效果,是這樣的:

下面再來說明內存洩漏的問題,把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的格式:

父子組件的created和mounted的區別, 按執行順序:

所以,到底在哪個鉤子中訂閱,在哪個鉤子中發布,要根據情況來定。


小結

eventBus是一個名詞,並非前端獨有;

new Vue() 得到的實例上已經實現了發布訂閱模式,可以直接做eventBus使用;

使用eventBus要及時調用$off;


版權資訊:凡人進階。轉載請標明出處。如果對您有幫助,您可以:

相關焦點

  • Vue組件的通信--eventBus
    為此,我們可以創建一個快速的解決方案並實現EventBus,也稱作vue的中央事件總線,適用於跨級或兄弟組件。EventBus允許我們在一個組件中發出一個事件,而在另一個組件中偵聽該事件。本示例將說明如何在Vue.js應用程式中執行此操作。首先,我們需要一種在組件之間傳輸事件的機制。事實證明。
  • 你不可不知的前端進階知識:Vue事件總線(EventBus)使用詳細介紹
    vue組件非常常見的有父子組件通信,兄弟組件通信。而父子組件通信就很簡單,父組件會通過props向下傳數據給子組件,當子組件有事情要告訴父組件時會通過 $emit事件告訴父組件。今天就來說說如果兩個頁面沒有任何引入和被引入關係,該如何通信了?
  • vue數據傳遞--我有特殊的實現技巧
    有這麼幾種數據傳遞方式,vuex、props、eventBus和特殊的eventBus。vuex不介紹,數據量和複雜度達不到不用它你才會向下看。propsdemo父子組件傳值,官方api,只寫個demo。
  • 前端技術:開發一個vue中央事件總線插件vue-bus
    首先,我們使用vue-cli創建一個項目vue-bus, 在src目錄下,新建vue-bus.js文件,vue-bus插件像vue-router 和 Vuex一樣,給Vue對象添加一個屬性$bus,並代理$emit、$on、$off三個方法。
  • EventBus—事件總線
    Vue EventBus 源碼解析VUE中EventBus可以用來進行任何組件之間的通信。EventBus可以當成一個管道,這個管道兩端可以接好多組件,兩端的任何一個組件都可以進行通信。這個管道就是Vue實例,實例中有四個跟事件派發相關的方法$on $off $emit $once。
  • Netty堆外內存洩漏排查,這一篇全講清楚了
    上篇文章介紹了Netty內存模型原理,由於Netty使用不當會導致堆外內存洩漏,網上關於這方面的資料比較少,所以寫下這篇文章,基於Netty4.1.43.Final,專門介紹排查Netty堆外內存相關的知識點,診斷工具,以及排查思路現象堆外內存洩漏的現象主要是,進程佔用的內存較高(Linux
  • iOS 內存洩漏場景與解決方案
    在 iOS 開發中會遇到的內存洩漏場景可以分為幾類:循環引用當對象 A 強引用對象 B,而對象 B 又強引用對象 A,或者多個對象互相強引用形成一個閉環,這就是循環引用。BlockBlock 會對其內部的對象強引用,因此使用的時候需要確保不會形成循環引用。
  • 【221期】面試官:談談內存洩漏和內存溢出的聯繫與區別
    此外,內存洩漏不會直接產生可觀察的錯誤,而是逐漸積累,降低系統的整體性性能。4、如何有效的進行內存分配和釋放,防止內存洩漏,是軟體開發人員的關鍵問題,比如一個伺服器應用軟體要長時間服務多個客戶端,若存在內存洩漏,則會逐漸堆積,導致一系列嚴重後果。
  • 線程上下文類加載器ContextClassLoader內存洩漏隱患
    前提在編寫Netty相關代碼的時候,從Netty源碼中的ThreadDeathWatcher和GlobalEventExecutor追溯到兩個和線程上下文類加載器ContextClassLoader內存洩漏相關的Issue:ThreadDeathWatcher
  • C語言中的指針和內存洩漏
    指針和內存洩漏對某些開發人員來說似乎令人畏懼,但是一旦您了解了指針及其關聯內存操作的基礎,它們就是您在 C 語言中擁有的最強大工具。本文將與您分享開發人員在開始使用指針來編程前應該知道的秘密。內存洩漏內存洩漏可能真正令人討厭。下面的列表描述了一些導致內存洩漏的場景。●重新賦值我將使用一個示例來說明重新賦值問題。char *memoryArea = malloc(10);char *newArea = malloc(10);這向如下面的圖 4所示的內存位置賦值。
  • vue兄弟組件傳值的兩種方式
    本demo主要是為了演示vue項目中兄弟組件之間的傳值,這裡我演示了兩種方式:a. bus總線傳值;b.我自己一般把它當成常規的傳值(其實也就是子組件A傳父組件,父組件再傳子組 件B)下邊開始本次demo的編寫:一. bus總線傳值的使用:在項目中創建一個單獨的eventBus.js文件
  • 理解閉包與內存洩漏
    所以形成的閉包條件就是,存在內部函數中使用外部函數中定義的變量。三、內存洩漏內存洩漏常常與閉包緊緊聯繫在一起,很容易讓人誤以為閉包就會導致內存洩漏。其實閉包只是讓內存常駐,而濫用閉包才會導致內存洩漏。所以我們通常會重複多次執行某段邏輯鏈路,然後每隔一段時間進行一次內存dump,然後判斷內存是否存在不斷增長的趨勢,如果存在,則可用懷疑存在內存洩漏的可能。
  • Android 內存洩漏探討
    在堆中分配的內存,將由 Java 垃圾回收器來自動管理。在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,這個特殊的變量就是我們上面說的引用變量。我們可以通過這個引用變量來訪問堆中的對象或者數組。
  • JavaScript內存管理機制以及四種常見的內存洩漏解析
    這是因為在數組中訪問一個不存在的元素(它比數組中最後一個實際分配的元素x[3]還要大4個字節),最終可能會讀取(或重寫) 到m的位,這肯定會對程序的其餘部分產生不可預知的結果。循環會產生問題當涉及到循環時,會有一個限制。在下面的示例中,創建了兩個對象,兩個對象互相調用,從而創建了一個循環。在函數調用之後將超出作用域,因此它們實際上是無用的,可以被釋放。然而,引用計數算法認為,由於每個對象至少被引用一次,所以它們都不能被垃圾收集。
  • C語言內存洩漏,看完這篇就懂了!
    說明:預防內存洩漏問題有多種方法,如加強代碼檢視、工具檢測和內存測試等,本文聚集於開發人員能力提升方面。堆內存在C代碼中的存儲方式內存洩漏問題只有在使用堆內存的時候才會出現,棧內存不存在內存洩漏問題,因為棧內存會自動分配和釋放。
  • 用valgrind定位內存洩漏
    緣起年前,寫了使用mtrace定位內存洩漏,在留言中,有讀者提到了希望介紹valgrind,那好,今天就介紹使用valgrind定位內存洩漏。大約2-3年前,楊同學讓我幫做模擬面試,他求職的是C++崗位,我問了這樣一個問題:在你的項目中,你是如何定位內存洩漏的呢?
  • 檢查是否內存洩漏的方法
    目錄前言一個簡單的內存洩漏檢查內存洩露的方法方法內存洩漏時,內存的消耗沒有內存洩露時的內存消耗前言這兩天抽空把之前買的這門課給看了看,課程很好,有興趣的也可以看一看:具體的課程總結過幾天寫一寫,這篇推送就說一下從這課上學到的一個技巧吧:一個檢查內存洩露的方法。
  • Android 輕鬆解決內存洩漏
    與 Android 中的差異:在 2.3 以後版本中,即使內存夠用,Android 系統會優先將 SoftReference 的對象提前回收掉, 其他和 Java 中是一樣的。因此谷歌官方建議用LruCache(least recentlly use 最少最近使用算法)。會將內存控制在一定的大小內, 超出最大值時會自動回收, 這個最大值開發者自己定。
  • vue中的生命周期
    簡單來說,vue生命周期就是我們在瀏覽器中打開和關閉頁面的過程中,vue實例的創建和銷毀的全過程。vue裡提供了一些生命周期鉤子函數,使得開發者可以在實例創建的某個階段上插入自定義代碼,從而實現響應的功能處理。
  • iOS內存洩漏的檢測及定位工具-Instruments
    經常利用Xcode進行debug測試,某次測試debug時APP突然閃退,控制臺列印log最後一行提示:「Terminated due to memory issue」原來是內存問題導致,後來經過和開發小哥哥溝通,分析得知可能是內存洩漏,於是利用Instruments工具進行檢測發現果然存在內存洩漏,並成功定位到內存洩漏的代碼所在,問題完美解決。