技術乾貨|Vue頁面的內存洩露分析

內存洩露是指new了一塊內存,但無法被釋放或者被垃圾回收。

new了一個對象之後,它申請佔用了一塊堆內存,當把這個對象指針置為null時或者離開作用域導致被銷燬,那麼這塊內存沒有人引用它了在JS裡面就會被自動垃圾回收。

但是如果這個對象指針沒有被置為null,且代碼裡面沒辦法再獲取到這個對象指針了,就會導致無法釋放掉它指向的內存,容易造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重後果。

一、module閉包

1
2
3
4
5
6
7
8
9
10
11
// module date.js
let date = null;
export default {
init () {
date = new Date();
}
}
// main.js
import date from 'date.js';
date.init();

在 mainjs文件 初始化了date之後,date這個變量就一會直存在了,直到你把頁面關了 因為date的引用是在另一個module裡面,可以理解為模塊就是一個閉包對外是不可見的。

二、事件綁定 -> 閉包


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 一個圖片懶惰加載引擎示例
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'));
});

這裡就發生了內存洩露,主要是以下3行代碼導致的:

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


因為這裡的事件綁定形成了一個閉包,this/$photoList這兩個變量一直沒有被釋放, this是指向ImageLazyLoader的實例,而$photoList是指向DOM結點。

解決方法: 銷燬實例的時候把綁定的事件off掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

23
24
25
26
27
28
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引擎檢測到那個閉包沒用了,就把那個閉包銷燬了,那麼閉包引用的外部變量也自然會被置空。

三、綁定在window上的函數


1
2
3
4
5
6
7
8
9
mounted () {
window.addEventListener('resize', this.getScale);
}

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

四、$on綁定


1
2
3
4
5
6
7
8
9
mounted () {
EventBus.$on('goToNextHomeworkTask', this.go2NextQuestion);
}

destroyed () {
EventBus.$off('goToNextHomeworkTask', this.go2NextQuestion);
}

五、$store的watch監聽


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mounted () {
this.$store.watch(state => state.currentIndex, (newIndex, oldIndex) => {
if (this.$refs.animation && newIndex === this.task.index - 1) {
this.$refs.animation.beginElement();
}
});
}

mounted () {
this.unwatchStore = this.$store.watch(state => state.currentIndex, (newIndex, oldIndex) => {
// 代碼略
});
},
destroyed () {
this.unwatchStore();
}

六、動畫


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
let loadingAnimate = null;
let bodymovinAnimate = {

// 顯示loading動畫
showLoading () {
loadingAnimate = bodymovinAnimate._showAnimate();
return loadingAnimate;
},
// 停止loading動畫
stopLoading () {
loadingAnimate && bodymovinAnimate._stopAnimate(loadingAnimate);
},
// 開始lottie動畫
_showAnimate () {
const animate = lottie.loadAnimation({
// 參數省略
});
return animate;
}
// 結束lottie動畫
_stopAnimate (animate) {
animate.stop();
let $container = $(animate.wrapper).closest('.bodymovin-container');
$container.remove();
},
};
export default bodymovinAnimate;

// 結束lottie動畫
_stopAnimate (animate) {
animate.destroy();
let $container = $(animate.wrapper).closest('.bodymovin-container');
$container.remove();
},
// 停止loading動畫
stopLoading () {
loadingAnimate && bodymovinAnimate._stopAnimate(loadingAnimate);
loadingAnimate = null;
},

所以綜合上面的分析,造成內存洩露的可能會有以下幾種情況:

1.監聽在window/body等事件沒有解綁

2.綁在EventBus的事件沒有解綁

3.Vuex的$store watch了之後沒有unwatch

4.模塊形成的閉包內部變量使用完後沒有置成null

5.使用第三方庫創建,沒有調用正確的銷燬函數

技術乾貨|Vue頁面的內存洩露分析

[ShareSDK] 輕鬆實現社會化功能 強大的社交分享

[SMSSDK] 快速集成短信驗證 聯結通訊錄社交圈

[MobLink] 打破App孤島 實現Web與App無縫鏈接

[MobPush] 快速集成推送服務 應對多樣化推送場景

[AnalySDK] 精準化行為分析 + 多維數據模型 + 匹配全網標籤 + 垂直行業分析顧問

BBSSDK | ShareREC | MobAPI | MobPay | ShopSDK | MobIM | App工廠

截止2018 年4 月Mob 開發者服務平臺全球設備覆蓋超過84 億,SDK下載量超過3,300,000+次,服務超過380,000+款移動應用


分享到:


相關文章: