一个Vue页面的内存泄露分析
⼀个Vue页⾯的内存泄露分析
(点击上⽅,可快速关注)
作者:李银城
www.yinchengli/2018/06/25/vue-memory-leak/
什么是内存泄露?内存泄露是指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('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掉,如下代码所⽰:dnf修罗90刷图加点
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引擎检测到那个闭包没⽤了,就把那个闭包销毁了,那么闭包引⽤的外部变量也⾃然会被置空。
好了,基础知识就讲解到这⾥,现在⽤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根结点的距离。上图展⽰的这些div 具体是啥呢?我们把⿏标放上去不动等个2s,它就会显⽰这个div的DOM信息:
通过className等信息可以知道它就是那个要检查的页⾯的DOM节点,在下⾯的Object的窗⼝⾥⾯依次展开它的⽗结点,可以看到它最外⾯的⽗结点是⼀个VueComponent实例:
除夕的风俗有哪些
下⾯黄⾊字体native_bind表⽰有个事件指向了它,黄⾊表⽰引⽤仍然⽣效,把⿏标放到native_bind上⾯停留2秒:
它会提⽰你是在homework-web.vue这个⽂件有⼀个getScale函数绑定在了window上⾯,查看⼀下这个⽂件确实是有⼀个绑定:
mounted (){
window.addEventListener('resize',Scale);
}
所以虽然Vue组件把DOM删除了,但是还有个引⽤存在,导致组件实例没有被释放,组件⾥⾯⼜有⼀个$el指向DOM,所以DOM也没有被释放。但是看代码的话是在beforeDestroyed⾥⾯解绑的:
beforeDestroyed (){
}
所以应该没有问题啊?
定睛⼀看,傻眼了,原来函数名写错了,应该是:
beforeDestroy (){
},

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。