起因
近期客户经常反馈系统崩溃的问题,尤其是在下午最频繁,经过自己的自测,发现系统tab关闭后内存并没有回收掉,目前我已经处理了,tab页签关闭后,手动清理keep-alive内的缓存,应该不存在内存泄漏的情况,看来还有其他地方的缓存没有清理掉。
定位问题
1.还原场景
公司项目是单页应用,所有的操作都在一个浏览器页签内操作,整个页面是通过Layou+子路由的方式布局的,路由层级达到4级,业务复杂繁琐。需要重新搭建一个纯净项目还原场景
2.写个demo
使用vue-cli创建项目,vue@2.7.9
,vue-router@3.6.5
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({render: h => h(App),router
}).$mount('#app')
App.vue
<template><router-view></router-view>
</template>
<script> export default {name: "App",
}; </script>
view/Page1、view/Page2、view/A1、view/A2
<template><div>Page1<router-view></router-view></div>
</template>
<script> export default {name: "Page1",
}; </script>
<template><div>Page2<router-view></router-view></div>
</template>
<script> export default {name: "Page2",
}; </script>
<template><div>组件view A1</div>
</template>
<script> export default {name: "A1",data() {return {a: new Array(20000000).fill(1), //大概80mb};},
}; </script>
<template><div>组件view A2</div>
</template>
<script> export default {name: "A2",data() {return {a: new Array(20000000).fill(1), //大概80mb};},
}; </script>
view/Layout.vue
<template><div><h1>Layout</h1><div class="box"><p>二级路由</p><router-link :to="{ name: 'A' }">A</router-link><br /><router-link :to="{ name: 'B' }">B</router-link></div><div class="box"><p>三级路由</p><router-link :to="{ name: 'AA' }">Page1</router-link><br /><router-link :to="{ name: 'BB' }">Page2</router-link></div><div class="box"><button @click="includeRemove()">清理keepalive缓存</button><br /><router-link to="/home">Home</router-link><br /></div><h1>keep-alive</h1>缓存页面:{{ include }}<keep-alive :include="include"><router-view ref="alive"></router-view></keep-alive></div>
</template>
<script> export default {name: "Layout",data() {return {include: [],};},watch: {'$route'(val) {const name = val.meta.nameif (name && !this.include.includes(name)) {this.include.push(name);}}},methods: {includeRemove() {this.include = [];},},mounted() {},
}; </script>
<style> .box {margin-bottom: 20px;
} </style>
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const router = new Router({mode: "hash",routes: [{path: "/",redirect: "/home",component: () => import("../view/Layout.vue"),children: [{path: "home",component: () => import("../view/Home.vue"),},],},{path: "/a",component: () => import("../view/Layout.vue"),children: [{path: "a",name: "A",meta: {name: 'A1'},component: () => import("../view/A1.vue"),},],},{path: "/b",component: () => import("../view/Layout.vue"),children: [{path: "b",name: "B",meta: {name: 'A2'},component: () => import("../view/A2.vue"),},],},{path: "/a",component: () => import("../view/Layout.vue"),children: [{path: "page1",name: "Page1",component: () => import("../view/Page1.vue"),children: [{path: "a",name: "AA",meta: {name: 'Page1'},component: () => import("../view/A1.vue"),},],},],},{path: "/b",component: () => import("../view/Layout.vue"),children: [{path: "page2",name: "Page2",component: () => import("../view/Page2.vue"),children: [{path: "b",name: "BB",meta: {name: 'Page2'},component: () => import("../view/A2.vue"),},],},],},],
});
export defaultrouter
运行效果
3.重现问题
可以看出,初始情况下,内存使用7.7MB左右
1.点击A、B后,内存占用168MB
2.点击Home,保证在清理缓存时,路由不占用A,B组件,再次点击清理keepalive缓存
手动GC后,发现内存使用变为7.9MB,说明A、B组件成功释放掉了,这个模拟了公司项目前期只有二级路由的情况,那个时候还不存在系统崩溃的问题,这里也刚好印证了。
3.点击Page1、Page2,内存占用是168MB
4.点击Home,再次点击清理keepalive缓存
这个时候就出问题了,内存并没有成功的释放掉,问题找到了
4.分析
首先记录初始情况下内存占用
打开Page1、Page2,切换到Home页面,清理keepalive缓存,记录当前内存快照
从图中可以看出,A1组件还存在,并且是vue-router引用了,nameMap保存了所有的路由信息,这样的话问题就找到了
初始状态下路由信息
打开Page1、Page2,切换到Home页面,清理keepalive缓存后路由信息
测试另外一种情况,清理keepalive缓存前不切换到Home,这种情况下,内存是可以成功释放掉的。
5.结论
1.如果是在当前路由关闭tab,然后清理keepalive缓存,内存是可以正常回收的
2.如果是在其他路由关闭非激活的路由时,二级路由组件可以正常回收,二级以下路由内存回收异常,猜测非激活路由matched内的信息以变更了,毕竟是单例模式,这就说的通,为啥激活的路由移除缓存是正常的了
3.修复问题
1.思路
1.获取关闭当前tab路由父子关系
2.通过所有的路由信息,遍历删除相关路由的instances.default
值
2.具体代码实现
includeRemove() {this.include = [];// 为啥vue-router不开放直接获取nameMap的接口 淦const routes = this.$router.getRoutes()const nameMap = new Map()for (let index = 0; index < routes.length; index++) {const r = routes[index];nameMap.set(r.name, r)}// 假设我这边获取到了当前移除的tab页签,具体代码根据具体项目实现const rList = ['AA', 'BB']for (let index = 0; index < rList.length; index++) {const name = rList[index];const r = nameMap.get(name)if (r) r.instances.default = undefined}
}
代码改造后重新按照流程走了一遍,内存使用情况如下,成功解决问题
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享