watch里面有一个this指针指向了组件的DOM元素,由于子组件没有被释放,那么包含它的父组件自然不会被释放,所以一层层往上,导致最外面那个路由组件也不会被释放。
这个需要在destroyed的时候unwatch一下:
mounted () { this.unwatchStore = this.$store.watch(state => state.currentIndex, (newIndex, oldIndex) => { // 代码略 }); }, destroyed () { this.unwatchStore(); }
处理完之后再拍一张内存快照,如下图所示:
虽然还是74个但是distance已经为空了,可对比前3步distance都不为空,并且下面Object展开没有找到标黄的部分了,也就是说这个路由组件内存泄露的问题已经得到解决。
我们继续查看其它distance不为空的div节点,如下图所示,可以按照distance排下序:
其中有一个是.animate-container:
它是一个用来放lottie动画的DOM容器,lottie对象里面仍有引用它:
这个是一个用lottie做的loading动画,当loading结束的时候,我会手动调一下它的stop api停止动画,并且把.animte-container给remove掉,但是为什么lottie还不肯放过它呢?我的代码是这么写的:
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;
我猜想是调了stop之后lottie仍然没有释放对DOM的引用,因为stop之后还能够够支持重新start的,所以它得咬着DOM不放,因此如果要彻底结束动画,应该不是调stop,查了一下它还有一个destroy的方法,把stop换成destroy:
// 结束lottie动画 _stopAnimate (animate) { animate.destroy(); let $container = $(animate.wrapper).closest('.bodymovin-container'); $container.remove(); },
这样改了之后,lottie的引用就会把它给释放了,问题解决了,然后再重新拍一张照片:
仍然有一个exports.default指向它,它是webpack的模块,我猜想是因为本文开篇提到的例子的原因,就是模块形成了闭包,它的变量没有被释放造成内存泄露,所以在stopLoading里面把它置成null:
// 停止loading动画 stopLoading () { loadingAnimate && bodymovinAnimate._stopAnimate(loadingAnimate); loadingAnimate = null; },
这样试了之后,.animate-container这个DOM对象就没有人引用它了。
最后div还剩下3个有distance:
其中两个是jq的$.support.boxSizingReliable,是jq用来检测boxszing是否可用创建的div:
还有一个是Vue的:
这些都是使用的库造成的内存泄露,暂时先不管。
再去分析其它的标签也有类似的情况。
所以综合上面的分析,造成内存泄露的可能会有以下几种情况:
(1)监听在window/body等事件没有解绑
(2)绑在EventBus的事件没有解绑
(3)Vuex的$store watch了之后没有unwatch
(4)模块形成的闭包内部变量使用完后没有置成null
(5)使用第三方库创建,没有调用正确的销毁函数
并且可以借助Chrome的内存分析工具进行快速排查,本文主要是用到了内存堆快照的基本功能,读者可以尝试分析自己的页面是否存在内存泄漏,方法是做一些操作如弹个框然后关了,拍一张堆快照,搜索detached,按distance排序,把非空的节点展开父级,找到标黄的字样说明,那些就是存在没有释放的引用。也就是说这个方法主要是分析仍然存在引用的游离DOM节点。因为页面的内存泄露通常是和DOM相关的,普通的JS变量由于有垃圾回收所以一般不会有问题,除非使用闭包把变量困住了用完了又没有置空。