记录一次前端页面崩溃的产生及处理

记录一次前端页面崩溃的产生及处理

​  起因:前端的一个地图页面某一些单子一点进去,就会导致页面卡死、崩溃,浏览器最终给的错误码为: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 环境可能对此种情况做出了一定的优化,导致问题没能复现,具体原因也未得而知。

​  补充:页面崩溃前的情况:其他元素貌似渲染完毕,只剩下地图元素还没进行渲染,这一定程度误导了我们对于该问题产生原因的推测。

上一篇:Android Gradle plugin 版本和Gradle 版本


下一篇:【AIGC-数字人】V-Express:渐进式训练的数字人视频生成技术