赞
踩
记录一次前端页面崩溃的产生及处理
起因:前端的一个地图页面某一些单子一点进去,就会导致页面卡死、崩溃,浏览器最终给的错误码为:out of memory。
排查:OOM!第一反应是猜测会不会是因为地图要渲染的点位太多了(大概7、8千个点位),后面在另一个页面看到相同数量的点位也能够正常的渲染出来,就排除了这一猜测。
由于这个情况只发生测试环境,本地环境复现不出来,一时陷入了困境。后面对比了两个环境的区别,测试环境是通过打包,使用 nginx 代理的方式运行的,本地环境是通过 node 环境运行。于是本地环境也采用打包,nginx 代理的方式运行,终于成功的把页面崩溃情况复现出来。
第一次代码定位:通过查看网络请求,发现页面卡死前有个请求一直处于未返回状态,于是使用接口调试工具手动调用该接口,但该接口能正常返回,排除接口问题。接着将代码定位到发送该请求处,在进行各种各样的尝试后发现,将 statusList 从响应式类型换成非响应式类型后,居然页面不会崩溃了,于是便再发了一版测试环境…
...
<div v-for="(item, index) in statusList" :key="index" style="width: 100%">
...
</div>
...
const statusList = ref<any>();
const draw = async () => {
...
statusList.value = await getStatus();
...
}
解决方案:将响应式类型换成非响应式类型…
再起:发版没过多久,这个状况又出现了,不同之处在于,这次刚点进去没问题,过了一段时间才会出现页面崩溃的情况。
排查:这次情况第一反应,内存泄漏!通过内存工具发现,页面快照的内存会随着时间的增加而增加。这次首先去优化了地图元素的使用,没用的元素该销毁的进行销毁,不出意外,没啥效果。又是一顿各种尝试,终于发现了问题所在!
第二次代码定位:通过打 log,发现某一方法(getShipRoute)一直在死循环不断输出 log,于是定位到了该代码处。
...
<div v-for="(item, index) in statusList" :key="index" style="width: 100%">
<div v-if="getShipRoute(Number(items.pointIndex) - 1)">
...
<span class="arr-time">船名:{{ shipRoute.vesselName }}</span>
...
</div>
</div>
...
const shipRoute = ref<any>(null);
const getShipRoute = (idx:number) => {
let a = routeList.find((v: any) =>{
return v.range === idx
});
if (a) shipRoute.value = a;
return a;
}
原因:shipRoute 是一个响应式数据,内部是一个对象,第一次循环,将 a1 的引用赋值给 shipRoute,然后 shipRoute 进行第一次页面元素 pageElement1 渲染,第二次循环,将 a2 的引用赋值给 shipRoute,然后 shipRoute 进行第二次页面元素 pageElement2 渲染,同时由于 shipRoute 发生改变, pageElement1 需要重新进行一次渲染,又将 a1 的引用赋值给 shipRoute,此时 shipRoute 对于 pageElement2 来说又发生了变化,至此死循环发生了。
解决方法:将 shipRoute 的内部数据换成数组,至此该问题得到了解决。
疑问点:
对于疑问1,推测跟渲染方式有关,如果外层数组为响应式数据,每次死循环都得渲染外层整一个代码块,这无疑大大加速了内存的爆炸,而不为响应式数据,则每次死循环只需要渲染内层一个小代码块,内存增长的速度不会那么快。但随着时间的累积作用,内存也会增长到极限值进而引发崩溃,这似乎解释了为什么第一次改动可以延迟问题的发生。
对于疑问2,猜测 node 环境可能对此种情况做出了一定的优化,导致问题没能复现,具体原因也未得而知。
补充:页面崩溃前的情况:其他元素貌似渲染完毕,只剩下地图元素还没进行渲染,这一定程度误导了我们对于该问题产生原因的推测。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。