近来在对vue项目进行优化的时候,发现使用了百度地图组件会引起内存泄露的问题,组件在关闭后没有释放相应的内存,每次打开这个组件,内存都会增加。

分析出现泄露的原因

项目架构是vue单页面应用,直接在index.html中通过script标签全局引入百度地图的SDK,然后在用到地图的组件的mounted生命周期中new创建地图实例,下面用一个demo复现一下:

在这里插入图片描述

利用vue动态组件,通过点击不同按钮切换不同的组件,然后在Chrome控制台中的Memory中截取快照。

在这里插入图片描述
在这里插入图片描述

快照1是页面初始化完成后获取的,快照4是页面经过了多次切换组件操作后获取的,可以看出多次切换后内存却在不断增加,出现了内存泄露问题

在快照中ShallowSizeRetained Size对于对象来说表示:自身占用的内存大小和对象中引用的其他对象或者基本类型所占用的内存大小;而对于基本类型来说,ShallowSizeretainedSize是相等的,都是自身所占用的内存大小。这里关注的重点是RetainedSize,这反映了变量被GC后可以释放多少其他引用变量的内存,而优化的目标就是要释放RetainedSize过高的变量。

另一个关注点是Detached HTMLElement,在页面中有的dom已经不存在了(被remove等),但是因为JS中仍有对这些dom节点的引用,所以页面上虽然没有这些dom结点了,在内存中仍然保留了这些dom节点的引用,就出现了独立于HTML文档之外的dom结点,Detached HTMLElement也会引发内存泄露。

注:这里推荐阅读 Detached window memory leaks,对Detached HTMLDocument引发内存泄露原因进行了比较详细的介绍

回到之前截取的快照中,在快照4中可以发现Detached HTMLDivElement中出现了许多RetainedSize较大的对象,点击其中一个定位到哪些变量引用导致了内存无法释放
在这里插入图片描述

发现在window对象中存在一个全局变量$BAIDU$对这个dom节点引用,在控制台打印一下这个全局变量:

在这里插入图片描述
可以看到在instance下出现了很多对象,Na对象就是通过new BMap.map()构造的地图实例。正因为这个全局变量中存在地图实例的引用,所以很多地图实例都在组件切换后无法释放内存,其所引用的dom节点也就无法释放。

其他的Detached HTMLElement这里就不展开叙述了,通过一番查找,最终发现了百度地图在创建地图实例的时候,在window对象上绑定了不少全局变量(当然不止$BAIDU$),这点也可以在百度地图的getScript.js中得到映证。正是这些全局变量间接或者直接持有地图实例的引用,而地图实例中又存在对dom节点的引用,所以导致了,频繁切换组件后出现了大量Detached HTML,进而引发内存泄露的问题。

寻找解决的可行方案

这里,你可能会想,既然找到了这个问题,那直接在beforeDestroy中把这些地图实例销毁不就完了。然而,并不是这么简单,前提是你能找出百度地图中所有地图实例的引用并且不影响百度地图SDK的正常使用,然后通过变量置空来触发GC

并且我的项目中使用的是百度地图js2.0,官方并没有提供destroy的方法来销毁实例,而网传的百度地图destroy方法其实是百度地图webgl1.0版本中提供的。

在这里插入图片描述

也有人可能会想,是不是我们使用方式不对,GitHub上有Vue Baidu Map组件,经过测试这种通过安装依赖的方式使用百度地图并不能改变内存泄露,其原理还是引用了百度地图js2.0,还是会在window对象上创建地图实例的引用,并无法销毁。

针对百度地图的内存泄露问题,我想出了几种解决方案:

方案 1
升级百度地图js,使用最新的百度地图webgl1.0版本,这个版本提供了destroy方法,可以在不使用地图的时候手动销毁地图实例,升级SDK可能需要重构代码

方案 2
既然使用new BMap.Map()创建地图实例会在window对象上绑定引用,那么将地图放在一个独立的html中,在vue组件中使用iframe引入不就可以了,只要保证vue组件销毁时能关闭iframe就可以释放地图实例这部分内存了,比较麻烦的就是vue页面和iframe中通信的问题,我了解到的就是使用postMessage事件传递消息

除了这些之外,可以尝试一下使用高德地图sdk,毕竟高德地图有for vue的用法,并有比较详细的文档,估计有考虑过内存泄露这样的低级错误的。

Logo

前往低代码交流专区

更多推荐