一个Vue页面的内存泄露分析详解(2)

这里我是第2种方式的场景,检测单页面应用的某个路由页面是否存在内存泄露。先打开首页,点到另一个页面,再点后退,接着点一下垃圾回收的按钮:

一个Vue页面的内存泄露分析详解

触发垃圾回收,避免一些不必要的干扰。

然后再点一下拍照按钮:

一个Vue页面的内存泄露分析详解

它就会把当前页面的内存堆扫描一遍显示出来,如下图所示:

一个Vue页面的内存泄露分析详解

然后在上面中间的Class Filter的搜索框里搜一下detached:

一个Vue页面的内存泄露分析详解

它就会显示所有已经分离了DOM树的DOM结点,重点关注distance值不为空的,这个distance表示距离DOM根结点的距离。上图展示的这些div具体是啥呢?我们把鼠标放上去不动等个2s,它就会显示这个div的DOM信息:

一个Vue页面的内存泄露分析详解

通过className等信息可以知道它就是那个要检查的页面的DOM节点,在下面的Object的窗口里面依次展开它的父结点,可以看到它最外面的父结点是一个VueComponent实例:

一个Vue页面的内存泄露分析详解

下面黄色字体native_bind表示有个事件指向了它,黄色表示引用仍然生效,把鼠标放到native_bind上面停留2秒:

一个Vue页面的内存泄露分析详解

它会提示你是在homework-web.vue这个文件有一个getScale函数绑定在了window上面,查看一下这个文件确实是有一个绑定:

mounted () { window.addEventListener('resize', this.getScale); }

所以虽然Vue组件把DOM删除了,但是还有个引用存在,导致组件实例没有被释放,组件里面又有一个$el指向DOM,所以DOM也没有被释放。

但是看代码的话是在beforeDestroyed里面解绑的:

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

所以应该没有问题啊?

定睛一看,傻眼了,原来函数名写错了,应该是:

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

发现了一个隐藏多日的bug,因为这个比较隐蔽,就算写错了也不会有明显的感知了。

把这个地方改一下,重复操作一遍,再拍一张内存快照。我们发现游离的div节点仍然是74个且disance不为空,没有改进如下图所示:

一个Vue页面的内存泄露分析详解

难道刚刚改得不对?继续查看刚刚第2个节点:

一个Vue页面的内存泄露分析详解

可以发现,这次是有一个 事件总线EventBus的事件绑定指向了它 ,说明除了刚刚那个resize事件绑定之外,还有一个EventBus的事件没有释放,事件名称是gToNextHomworkTask。我们搜一下这个事件是在哪里绑的,可以找到它是在路由组件的一个子组件里面绑的:

mounted () { EventBus.$on('goToNextHomeworkTask', this.go2NextQuestion); }

果不其然,这个组件只有$on,没有$off,所以导致组件卸载的时候仍然有一个事件的引用。所以需要在这个组件的destroyed里面给$off掉:

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

改完后刷新页面操作第3次,再拍一张内存快照,比较尴尬的是情况还是一样:

一个Vue页面的内存泄露分析详解

说明还有人引用它,继续查看是谁引用了没有释放:

一个Vue页面的内存泄露分析详解

可以发现是一个 Vuex的$store的watch监听没有释放 ,借助Watcher的cb属性可以知道具体是哪个监听函数。利用简单的文本搜索发现是在一个子组件里面进行了watch:

mounted () { this.$store.watch(state => state.currentIndex, (newIndex, oldIndex) => { if (this.$refs.animation && newIndex === this.task.index - 1) { this.$refs.animation.beginElement(); } }); }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/3d4e1d71b14710faf56dc64770bcf78e.html