內存泄露---iview中Split 面板分割組件

前言

最近產品經理跟我反饋公司的一個系統頻繁切換主菜單時,瀏覽器佔用內存一直在飆升。當時第一反應內存泄露了。 通過場景重現,發現從A主菜單切到B主菜單,在切回到A主菜單,瀏覽器佔用內存翻了一倍,基本能夠確認是內存泄露。瀏覽器

1、排查過程

根據BUG場景重現,瀏覽器佔用內存是翻了一倍,能夠初步肯定是Vue組件沒被釋放掉。bash

在Vue中,若是一個Vue組件存在內存泄露,那和該組件相互引用的組件也不能被銷燬掉。爲何會這樣能夠看下面解釋?app

首先要明白Vue組件最後會被渲染成一個DOM樹,好比一個表格組件中是由多個單元格組件構成,若是你在JS代碼中保留對一個單元格的引用。在未來你決定將這個表格從DOM中移除,仍舊保留這個單元格的引用。你可能會認爲 GC 會回收除了這個單元格以外全部的東西,可是實際上不會。單元格是表格的一個子節點且全部子節點都保留着它們父節點的引用。換句話說,JS代碼中對單元格的引用致使整個表格被保留在內存中。iview

因此用Chrome開發者工具中Memory功能排查,你會發現怎麼每一個組件都存在內存泄漏,致使無從下手。函數

這時候咱們就要採用排除法來找出泄露點,以前說過是切換主菜單時發生內存泄漏,那首先把每一個菜單頁面內容全註釋,再切換主菜單,經測試沒有內存泄漏。工具

把A主菜單頁面中一個個組件打開註釋,切換A、B主菜單測試,很幸運發現泄露點,Split 面板分割組件存在內存泄漏。

爲了排除其餘影響,單獨起一個測試項目,引進iview中Split 面板分割組件,切換路由,發現有內存泄漏。測試

查看項目中iview是3.4.2版本,上GitHub查看源碼。this

在mounted鉤子函數中有個,window綁定resize的監聽,可是沒有在beforeDestroy鉤子函數中解綁,將該組件代碼複製到測試項目中,改爲spa

methods:{
    handleUp () {
        this.isMoving = false;
        document.removeEventListener('mousemove',this.handleMove,false);
        document.removeEventListener('mouseup',this.handleUp,false);
        this.$emit('on-move-end');
    },
    handleMousedown (e) {
        this.initOffset = this.isHorizontal ? e.pageX : e.pageY;
        this.oldOffset = this.value;
        this.isMoving = true;
        document.addEventListener('mousemove',this.handleMove,false);
        document.addEventListener('mouseup',this.handleUp,false);
        this.$emit('on-move-start');
    },
    computeOffset(){
        this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100;
    }
},
watch: {
    value () {
        this.computeOffset();
    }
},
mounted () {
    this.$nextTick(() => {
        this.computeOffset();
    });
    window.addEventListener('resize',this.computeOffset);
},
beforeDestroy(){
    window.removeEventListener('resize',this.computeOffset);
}
複製代碼

從新測試,發現內存泄漏消失3d

回到原來項目中,將iview中Split 面板分割組件替換成修改好的組件,經測試,沒有發生內存泄漏。

2、關於移除事件監聽的坑

在iview中Split 面板分割組件中有這樣的代碼

mounted () {
    this.$nextTick(() => {
        this.computeOffset();
    });
    window.addEventListener('resize', ()=>{
        this.computeOffset();
    });
}
複製代碼

那在beforeDestroy中要怎麼移除這個事件監聽

beforeDestroy(){
    window.removeEventListener('resize', ()=>{
        this.computeOffset();
    });
}
複製代碼

或者

beforeDestroy(){
    window.removeEventListener('resize',this.computeOffset)
}
複製代碼

經測試,你會發現內存泄漏仍是沒解決,你會以爲很奇怪,明明已經移除事件監聽,爲啥還會內存泄漏,其實這邊有個坑在這邊,下面就給你們介紹一下。

若是要移除事件監聽,必須知足如下幾點

  1. addEventListener() 的執行函數必須使用外部函數,例: window.addEventListener('resize',this.computeOffset)

  2. 執行函數不能是匿名函數,例:window.addEventListener('resize', ()=>{this.computeOffset()})

  3. 執行函數不能改變this指向,例:window.addEventListener('resize',this.computeOffset.bind(this))

  4. addEventListenerremoveEventListener第三個參數必須一致,例:

    window.addEventListener('resize',this.computeOffset,true);
    window.removeEventListener('resize',this.computeOffset,true);
    複製代碼
    window.addEventListener('resize',this.computeOffset,false);
    window.removeEventListener('resize',this.computeOffset,false);
    複製代碼
相關文章
相關標籤/搜索