vue使用中的內存泄漏【推薦】
今天看到一篇關于js使用中內存泄露的文章,以及chrom瀏覽器查看內存泄漏的方法,決定留著。本文只截取了我認為比較重要的部分,喜歡原文的小伙伴,請點擊文章下方的原文鏈接。
什么是內存泄露?內存泄露是指new了一塊內存,但無法被釋放或者被垃圾回收。new了一個對象之后,它申請占用了一塊堆內存,當把這個對象指針置為null時或者離開作用域導致被銷毀,那么這塊內存沒有人引用它了在JS里面就會被自動垃圾回收。但是如果這個對象指針沒有被置為null,且代碼里面沒辦法再獲取到這個對象指針了,就會導致無法釋放掉它指向的內存,也就是說發(fā)生了內存泄露。為什么代碼里面會拿不到這個對象指針了呢,舉一個例子:
// 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對象一直存在、需要一直使用的話,那么沒有問題,但是如果想用一次就不用了那就會有問題,這個對象一直在內存里面沒有被釋放就發(fā)生了內存泄露。
另一種比較隱蔽并且很常見的內存泄露是事件綁定,形成了一個閉包,導致一些變量一直存在。如下例子所示:
// 一個圖片懶惰加載引擎示例
class ImageLazyLoader {
constructor ($photoList) {
$(window).on('scroll', () => {
this.showImage($photoList);
});
}
showImage ($photoList) {
$photoList.each(img => {
// 通過位置判斷圖片滑出來了就加載
img.src = $(img).attr('data-src');
});
}
}
// 點擊分頁的時候就初始化一個圖片懶惰加載的
$('.page').on('click', function () {
new ImageLazyLoader($('img.photo'));
});
這是一個圖片懶惰加載的模型,每次點分頁的時候就會清掉上一頁的數(shù)據(jù)更新為當前頁的DOM,并重新初始化一個懶惰加載的引擎。它里面監(jiān)聽了scroll事件,對傳進來的圖片列表的DOM進行處理。每點一次分頁就會重新new一個,這里就發(fā)生了內存泄露,主要是以下3行代碼導致的:
$(window).on('scroll', () => {
this.showImage($photoList);
});
因為這里的事件綁定形成了一個閉包,this/$photoList這兩個變量一直沒有被釋放,this是指向ImageLazyLoader的實例,而$photoList是指向DOM結點,當清除掉上一頁的數(shù)據(jù)的時候,相關DOM結點已經(jīng)從DOM樹分離出來了,但是仍然還有一個$photoList指向它們,導致這些DOM結點無法被垃圾回收一直在內存里面,就發(fā)生了內存泄露。由于this變量也被閉包困住了沒有被釋放,所以還有一個ImageLazyLoader的實例發(fā)生內存泄露。
這個的解決方法比較簡單,就是銷毀實例的時候把綁定的事件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('data-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有構造函數(shù)但是沒有解構函數(shù),所以需要自己寫一個clear,在外面手動調一下clear。同時在事件的執(zhí)行過程的合適時機自動把事件給解綁了,上面是判斷如果所有的圖片都展示出來了那么就沒必要監(jiān)聽scroll事件了直接解綁了。這樣就能解決內存泄露的問題了,能夠觸發(fā)自動垃圾回收。
為什么把事件解綁了,就不會有閉包引用了呢?因為JS引擎檢測到那個閉包沒用了,就把那個閉包銷毀了,那么閉包引用的外部變量也自然會被置空。
好了,基礎知識就講解到這里,現(xiàn)在用Chrome devtools的內存檢測工具來實際操作一遍,方便發(fā)現(xiàn)頁面的一些內存泄露行為。為了避免裝給瀏覽器裝的一些插件造成影響,使用Chome的隱身模式頁面,它會把所有的插件都給禁掉。
然后打開devtools,切到Memory的tab,選中Heap snapshot,如下所示:
什么叫heap snapshot呢?翻譯一下就是堆快照,給當前內存堆拍一張照片。因為動態(tài)申請的內存都是在堆里面的,而局部變量是在內存棧里面,是由操作系統(tǒng)分配管理的是不會內存泄露了。所以關心堆的情況就好了。
然后做一些增刪改DOM的操作,如:
(1)彈一個框,然后把彈框給關了
(2)單頁面的點擊跳轉到另一個路由,然后再點后退返回
(3)點擊分頁觸發(fā)動態(tài)改DOM
就是先增加DOM,然后把這些DOM給刪了,看一下這些被刪除的DOM是否還有對象引用它們。
這里我是第2種方式的場景,檢測單頁面應用的某個路由頁面是否存在內存泄露。先打開首頁,點到另一個頁面,再點后退,接著點一下垃圾回收的按鈕:
觸發(fā)垃圾回收,避免一些不必要的干擾。
然后再點一下拍照按鈕:
它就會把當前頁面的內存堆掃描一遍顯示出來,如下圖所示:
然后在上面中間的Class Filter的搜索框里搜一下detached:
它就會顯示所有已經(jīng)分離了DOM樹的DOM結點,重點關注distance值不為空的,這個distance表示距離DOM根結點的距離。上圖展示的這些div具體是啥呢?我們把鼠標放上去不動等個2s,它就會顯示這個div的DOM信息:
通過className等信息可以知道它就是那個要檢查的頁面的DOM節(jié)點,在下面的Object的窗口里面依次展開它的父結點,可以看到它最外面的父結點是一個VueComponent實例:
下面黃色字體native_bind表示有個事件指向了它,黃色表示引用仍然生效,把鼠標放到native_bind上面停留2秒:
它會提示你是在homework-web.vue這個文件有一個getScale函數(shù)綁定在了window上面,查看一下這個文件確實是有一個綁定:
mounted () {
window.addEventListener('resize', this.getScale);
}
所以雖然Vue組件把DOM刪除了,但是還有個引用存在,導致組件實例沒有被釋放,組件里面又有一個$el指向DOM,所以DOM也沒有被釋放。
要在beforeDestroyed里面解綁的
beforeDestroyed () {
window.removeEventListener('resize', this.getScale);
}
所以綜合上面的分析,造成內存泄露的可能會有以下幾種情況:
(1)監(jiān)聽在window/body等事件沒有解綁
(2)綁在EventBus的事件沒有解綁
(3)Vuex的$store watch了之后沒有unwatch
(4)模塊形成的閉包內部變量使用完后沒有置成null
(5)使用第三方庫創(chuàng)建,沒有調用正確的銷毀函數(shù)
并且可以借助Chrome的內存分析工具進行快速排查,本文主要是用到了內存堆快照的基本功能,讀者可以嘗試分析自己的頁面是否存在內存泄漏,方法是做一些操作如彈個框然后關了,拍一張堆快照,搜索detached,按distance排序,把非空的節(jié)點展開父級,找到標黃的字樣說明,那些就是存在沒有釋放的引用。也就是說這個方法主要是分析仍然存在引用的游離DOM節(jié)點。因為頁面的內存泄露通常是和DOM相關的,普通的JS變量由于有垃圾回收所以一般不會有問題,除非使用閉包把變量困住了用完了又沒有置空。
DOM相關的內存泄露通常也是因為閉包和事件綁定引起的。綁了(全局)事件之后,在不需要的時候需要把它解綁。當然直接綁在div上面的可以直接把div刪了,綁在它上面的事件就自然解綁了。
相關文章
vscode 配置vue+vetur+eslint+prettier自動格式化功能
這篇文章主要介紹了vscode 配置vue+vetur+eslint+prettier自動格式化功能,本文通過實例代碼圖文的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
Vue項目中如何封裝axios(統(tǒng)一管理http請求)
這篇文章主要給大家介紹了關于Vue項目中如何封裝axios(統(tǒng)一管理http請求)的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-05-05

