问题描述:
一个有关地图编辑的使用threejs的这样的组件,在多次挂载销毁后,页面开始卡顿。
问题排查:
1. 首先在chrome dev tool中打开performance monitor面板,观察 JS head size、DOME Nodes、Js event listeners数据是否呈现增长趋势
观察了一段时间,发现JS head size并没有明显的增长,但是DOM Nodes、Js event listeners随着每次组件的销毁、组件的重新挂载一直呈现增加的趋势。
2. 在chrome dev tool中打开performance进行性能分析
在performance面板中我们录制大概60s性能分析
查看结果
首先我们看一下JS head size,观察到在1分钟的组件不断销毁和挂载的过程中,是收敛的,组件挂载是增长,组件卸载时释放,是正常现象。
其次我观察到DOM Nodes、Js event listeners在1分钟的组件不断销毁和挂载的过程中,呈现阶梯式的增长,且增长的幅度几乎一致,如下图。
初步分析是在组件卸载时有事件监听没有销毁。
3. 在chrome dev tool中打开memory面板中进行深层次的分析
在memory面板中,多录制几个快照
我发现heap size是不断增长的,确认是存在内存泄露的问题的。
现在我选取Snapshot5和Snapshot4进行对比,看一下,增长的是什么
注意我框出来的地方。重点看delta哪些在增长。其中 compiled code这种不用管。这是编辑好的代码。
我们看到 HTMLDivElement、EventListener、ResizeObserver、等这些的增长,都能看出来我们的分析是对的,确实很多监听事件没有清除。
接下来我们去清除这些事件,再次捕捉快照看一下对比。
解决问题
1. 确定 setTimeout、setInterval、requestAnimationFrame等在组件销毁时清除,requestAnimationFrame很容易遗漏
2. 确认 addEventListener的事件在组件销毁时移除了。
这里需要注意一下,最好通过 document.removeEventListener(eventname,fn)的方式去清除
我之前通过document.onclick = null的方式去清除,发现还是会出现内存泄露的问题。
3. threejs引入的dragControls、orbitControls、还有自己封装的controls等在组件销毁时,记得销毁这些控制器中的事件监听。比如dragControls提供了方法 deactivate 方法去清除监听。
4. 在组件销毁时确认清除了 ResizeObserver
确保了这些问题都处理后,再次用performance性能分析60s,查看结果
我们看到DOM Nodes、Js event listeners在组件销户和挂载过程中是收敛的,不会再呈现阶梯式增长。问题得到解决。
再次去memory面板中查看新的快照
我们看到清除了这些事件监听后,heap size不再增长了,我们再具体对比一下Snapshot5和Snapshot4
之前的那些增长项,都为0了。至此问题解决。
还有一个常识时,在组件销毁时,记得把threejs scene中的所以object清除了。