vue使用中的內存泄漏

今天看到一篇關於js使用中內存泄露的文章,以及chrom瀏覽器查看內存泄漏的方法,決定留着。本文只截取了我認爲比較重要的部分,喜歡原文的小夥伴,請點擊文章下方的原文連接。vue

什麼是內存泄露?內存泄露是指new了一塊內存,但沒法被釋放或者被垃圾回收。new了一個對象以後,它申請佔用了一塊堆內存,當把這個對象指針置爲null時或者離開做用域致使被銷燬,那麼這塊內存沒有人引用它了在JS裏面就會被自動垃圾回收。可是若是這個對象指針沒有被置爲null,且代碼裏面沒辦法再獲取到這個對象指針了,就會致使沒法釋放掉它指向的內存,也就是說發生了內存泄露。爲何代碼裏面會拿不到這個對象指針了呢,舉一個例子:
web

// 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對象一直存在、須要一直使用的話,那麼沒有問題,可是若是想用一次就不用了那就會有問題,這個對象一直在內存裏面沒有被釋放就發生了內存泄露。瀏覽器

另外一種比較隱蔽而且很常見的內存泄露是事件綁定,造成了一個閉包,致使一些變量一直存在。以下例子所示:bash

// 一個圖片懶惰加載引擎示例
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'));
});複製代碼

這是一個圖片懶惰加載的模型,每次點分頁的時候就會清掉上一頁的數據更新爲當前頁的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('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有構造函數可是沒有解構函數,因此須要本身寫一個clear,在外面手動調一下clear。同時在事件的執行過程的合適時機自動把事件給解綁了,上面是判斷若是全部的圖片都展現出來了那麼就不必監聽scroll事件了直接解綁了。這樣就能解決內存泄露的問題了,可以觸發自動垃圾回收。字體

爲何把事件解綁了,就不會有閉包引用了呢?由於JS引擎檢測到那個閉包沒用了,就把那個閉包銷燬了,那麼閉包引用的外部變量也天然會被置空。ui

好了,基礎知識就講解到這裏,如今用Chrome devtools的內存檢測工具來實際操做一遍,方便發現頁面的一些內存泄露行爲。爲了不裝給瀏覽器裝的一些插件形成影響,使用Chome的隱身模式頁面,它會把全部的插件都給禁掉。this

而後打開devtools,切到Memory的tab,選中Heap snapshot,以下所示:



什麼叫heap snapshot呢?翻譯一下就是堆快照,給當前內存堆拍一張照片。由於動態申請的內存都是在堆裏面的,而局部變量是在內存棧裏面,是由操做系統分配管理的是不會內存泄露了。因此關心堆的狀況就行了。

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

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

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

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

就是先增長DOM,而後把這些DOM給刪了,看一下這些被刪除的DOM是否還有對象引用它們。

這裏我是第2種方式的場景,檢測單頁面應用的某個路由頁面是否存在內存泄露。先打開首頁,點到另外一個頁面,再點後退,接着點一下垃圾回收的按鈕:


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

而後再點一下拍照按鈕:


它就會把當前頁面的內存堆掃描一遍顯示出來,以下圖所示:


而後在上面中間的Class Filter的搜索框裏搜一下detached:


它就會顯示全部已經分離了DOM樹的DOM結點,重點關注distance值不爲空的,這個distance表示距離DOM根結點的距離。上圖展現的這些div具體是啥呢?咱們把鼠標放上去不動等個2s,它就會顯示這個div的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相關的內存泄露一般也是由於閉包和事件綁定引發的。綁了(全局)事件以後,在不須要的時候須要把它解綁。固然直接綁在div上面的能夠直接把div刪了,綁在它上面的事件就天然解綁了。

原文地址

相關文章
相關標籤/搜索