首頁  >  文章  >  web前端  >  vue使用中的記憶體洩漏【推薦】_vue.js

vue使用中的記憶體洩漏【推薦】_vue.js

无忌哥哥
无忌哥哥原創
2018-07-12 14:04:311263瀏覽

記憶體外洩是指new了一塊內存,但無法被釋放或被垃圾回收。這篇文章主要介紹了vue使用中的記憶體洩漏,需要的朋友可以參考下

什麼是記憶體洩漏?內存外洩是指new了一塊內存,但無法被釋放或被垃圾回收。 new了一個物件之後,它申請佔用了一塊堆內存,當把這個物件指標置為null時或離開作用域導致被銷毀,那麼這塊記憶體沒有人引用它了在JS裡面就會被自動垃圾回收。但是如果這個物件指標沒有被置為null,且程式碼裡面沒辦法再取得到這個物件指標了,就會導致無法釋放掉它指向的內存,也就是說發生了記憶體外洩。為什麼程式碼裡面會拿不到這個物件指標了呢,舉個例子:

// module date.js
let date = null;
export default {
 init () {
  date = new Date();
 }
}
// main.js
import date from 'date.js';
date.init();

在main.js初始化了date之後,date這個變數就一會直存在了,直到你把頁面關了,因為date的引用是在另一個module裡面,可以理解為模組就是一個閉包對外是不可見的。所以如果你是希望這個date物件一直存在、需要一直使用的話,那麼沒有問題,但是如果想用一次就不用了那就會有問題,這個物件一直在內存裡面沒有被釋放就發生了內存洩漏。

另一種比較隱蔽且很常見的記憶體外洩是事件綁定,形成了一個閉包,導致一些變數一直存在。如下範例所示:

// 一个图片懒惰加载引擎示例
class ImageLazyLoader {
 constructor ($photoList) {
  $(window).on('scroll', () => {
   this.showImage($photoList);
  });
 }
 showImage ($photoList) {
  $photoList.each(img => {
   // 通过位置判断图片滑出来了就加载
   img.src = $(img).attr('src');
  });
 }
}
// 点击分页的时候就初始化一个图片懒惰加载的
$('.page').on('click', function () {
 new ImageLazyLoader($('img.photo'));
});

這是一個圖片懶惰載入的模型,每次點分頁的時候就會清除上一頁的資料更新為目前頁的DOM,並重新初始化一個懶惰載入的引擎。它裡面監聽了scroll事件,對傳進來的圖片清單的DOM進行處理。每點一次分頁就會重新new一個,這裡就發生了內存洩露,主要是以下3行程式碼導致的:

$(window).on('scroll', () => {
 this.showImage($photoList);
});

##因為這裡的事件綁定形成了一個閉包,this/$photoList這兩個變數一直沒有被釋放,this是指向ImageLazyLoader的實例,而$photoList是指向DOM結點,當清除掉上一頁的數據的時候,相關DOM結點已經從DOM樹分離出來了,但仍然還有一個$photoList指向它們,導致這些DOM結點無法被垃圾回收一直在內存裡面,就發生了內存洩露。由於this變數也被閉包困住了沒有被釋放,所以還有一個ImageLazyLoader的實例發生記憶體洩漏。

這個的解決方法比較簡單,就是銷毀實例的時候把綁定的事件off掉,如下程式碼所示:

##
class ImageLazyLoader {
 constructor ($photoList) {
  this.scrollShow = () => {
   this.showImage($photoList);
  };
  $(window).on('scroll', this.scrollShow);
 }
 // 新增一个事件解绑       
 clear () {      
  $(window).off('scroll', this.scrollShow);
 }
 showImage ($photoList) {
  $photoList.each(img => {
   // 通过位置判断图片滑出来了就加载
   img.src = $(img).attr('src');
  });
  // 判断如果图片已全部显示,就把事件解绑了
  if (this.allShown) {
   this.clear();
  }
 }
}
// 点击分页的时候就初始化一个图片懒惰加载的
let lazyLoader = null;
$('.page').on('click', function () {
 lazyLoader && (lazyLoader.clear());
 lazyLoader = new ImageLazyLoader($('img.photo'));
});

在每次實例化一個ImageLazyLoader之前把上一個實例clear掉,clear裡面進行解綁,由於JS有構造函數但是沒有解構函數,所以需要自己寫一個clear,在外面手動調一下clear。同時在事件的執行過程的合適時機自動把事件給解綁了,上面是判斷如果所有的圖片都展示出來了那麼就沒必要監聽scroll事件了直接解綁了。這樣就能解決記憶體外洩的問題了,能夠觸發自動垃圾回收。

為什麼把事件解綁了,就不會有閉包引用了呢?因為JS引擎偵測到那個閉包沒用了,就把那個閉包銷毀了,那麼閉包引用的外部變數也自然會被置空。

好了,基礎知識就講解到這裡,現在用Chrome devtools的記憶體偵測工具來實際操作一遍,方便發現頁面的一些記憶體外洩行為。為了避免裝給瀏覽器裝的一些插件造成影響,使用Chome的隱身模式頁面,它會把所有的插件都給禁掉。

接著開啟devtools,切到Memory的tab,選取Heap snapshot,如下所示:

 


什麼叫heap snapshot呢?翻譯一下就是堆快照,給目前記憶體堆拍一張照片。因為動態申請的記憶體都是在堆裡面的,而局部變數是在記憶體棧裡面,是由作業系統分配管理的是不會記憶體洩漏了。所以關心堆的情況就好了。

然後做一些增刪改DOM的操作,如:

(1)彈一個框,然後把彈框給關了

(2)單頁的點擊跳到另一個路由,然後再點後退返回

(3)點選分頁觸發動態改DOM

就是先增加DOM,然後把這些DOM給刪了,看一下這些被刪除的DOM是否還有物件引用它們。

這裡我是第2種方式的場景,偵測單一頁面應用程式的某個路由頁面是否有記憶體外洩。先打開首頁,點到另一個頁面,再點後退,接著點一下垃圾回收的按鈕:

觸發垃圾回收,避免一些不必要的干擾。

然後再點一下拍照按鈕:

 

它就会把当前页面的内存堆扫描一遍显示出来,如下图所示:

 

然后在上面中间的Class Filter的搜索框里搜一下detached:

 

它就会显示所有已经分离了DOM树的DOM结点,重点关注distance值不为空的,这个distance表示距离DOM根结点的距离。上图展示的这些p具体是啥呢?我们把鼠标放上去不动等个2s,它就会显示这个p的DOM信息:

 

通过className等信息可以知道它就是那个要检查的页面的DOM节点,在下面的Object的窗口里面依次展开它的父结点,可以看到它最外面的父结点是一个VueComponent实例:

 

下面黄色字体native_bind表示有个事件指向了它,黄色表示引用仍然生效,把鼠标放到native_bind上面停留2秒:

它会提示你是在homework-web.vue这个文件有一个getScale函数绑定在了window上面,查看一下这个文件确实是有一个绑定:

mounted () {
 window.addEventListener('resize', this.getScale);
}

所以虽然Vue组件把DOM删除了,但是还有个引用存在,导致组件实例没有被释放,组件里面又有一个$el指向DOM,所以DOM也没有被释放。

要在beforeDestroyed里面解绑的

beforeDestroyed () {
 window.removeEventListener('resize', this.getScale);
}

所以综合上面的分析,造成内存泄露的可能会有以下几种情况:

(1)监听在window/body等事件没有解绑

(2)绑在EventBus的事件没有解绑

(3)Vuex的$store watch了之后没有unwatch

(4)模块形成的闭包内部变量使用完后没有置成null

(5)使用第三方库创建,没有调用正确的销毁函数

并且可以借助Chrome的内存分析工具进行快速排查,本文主要是用到了内存堆快照的基本功能,读者可以尝试分析自己的页面是否存在内存泄漏,方法是做一些操作如弹个框然后关了,拍一张堆快照,搜索detached,按distance排序,把非空的节点展开父级,找到标黄的字样说明,那些就是存在没有释放的引用。也就是说这个方法主要是分析仍然存在引用的游离DOM节点。因为页面的内存泄露通常是和DOM相关的,普通的JS变量由于有垃圾回收所以一般不会有问题,除非使用闭包把变量困住了用完了又没有置空。

DOM相关的内存泄露通常也是因为闭包和事件绑定引起的。绑了(全局)事件之后,在不需要的时候需要把它解绑。当然直接绑在p上面的可以直接把p删了,绑在它上面的事件就自然解绑了。

以上是vue使用中的記憶體洩漏【推薦】_vue.js的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn