前端vue项目内存泄漏排查总结
未完待续。。。
背景
- 项目由
vue-element-admin
改造,二次封装了element-ui
,组件套的有点深; - 系统上线一段时间后,业务频繁反馈页面崩溃;
- 项目使用了页面缓存,移除的话没问题,但因业务需要不能移除:
<transition name="fade-transform" mode="out-in"> <keep-alive :include="cachedViews"> <!-- 匹配三级路由,并缓存 --> <router-view :key="key" /> </keep-alive> </transition>
排查
- 使用谷歌开发工具面板
performance
、memory
排查,打快照,发现详情页内存一直累积,无法释放; - 列表页和详情页引用了公共组件(注意
import
引入的资源在内存中实质上是同一个对象); -
memory
排查分析分析累积的都是些vue组件; - 注意组件的
name
与路由的name
保持一致且唯一,不然用keep-alive
无法正常缓存(通过componentOptions.Ctor.options.name || componentOptions.tag
决定include
包含的组件缓存);
解决方案
- 缓存的页面走
keep-alive
,不缓存的把router-view
移到外面,即:
<transition name="fade-transform" mode="out-in">
<div>
<keep-alive :include="cachedViews" :max="10">
<!-- 匹配三级路由,并缓存 -->
<router-view v-if="!$route.meta.noCache" :key="key" />
</keep-alive>
<router-view v-if="$route.meta.noCache" :key="key" />
</div>
</transition>
- 比较粗暴的方法:主动销毁组件,详情页离开(销毁)之前,递归把组件的
componentInstance
、elm.innerHTML
移除,即把组件的联系断开,以便浏览器回收:
/**
* @name 深度销毁组件
* @description 用于没有使用 keep-alive 缓存的页面(详情页)
*/
export default {
beforeDestroy() {
async function destroyDeep(vnode) {
let vnodes
if (vnode.children || vnode.componentInstance?._vnode?.children) {
vnodes = vnode.children || vnode.componentInstance._vnode.children
for (const vn of vnodes) {
destroyDeep(vn)
}
}
vnode.componentInstance?.$destroy()
setTimeout(() => {
vnode.componentInstance = undefined
vnode.elm.innerHTML = ''
vnode.elm.__vue__ = undefined
}, 0)
}
destroyDeep(this._vnode)
}
}
- 把
vue
、vue-template-compiler
都升级到2.6.13
版本;
但有个情况依然有问题,就是两个不同列表(缓存)的详情页(不缓存)之间切换,详情页引用同一个详情组件的时候内存一样不释放。
keep-alive
组件部分源码:
vue@2.6.10
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance;
...
} else {
cache[key] = vnode;
...
}
vue@2.6.13
新增的cacheVNode
方法
if (vnodeToCache) {
var tag = vnodeToCache.tag;
var componentInstance = vnodeToCache.componentInstance;
var componentOptions = vnodeToCache.componentOptions;
cache[keyToCache] = {
name: getComponentName(componentOptions),
tag: tag,
componentInstance: componentInstance,
};
...
this.vnodeToCache = null;
}
为了减少篇幅,只把部分代码贴上;
vue@2.6.13
在mounted
、updated
都执行了this.cacheVNode()
。
这里this.vnodeToCache = null
,把vnode
的引用断开了!而旧版本的cache[key] = vnode
是没有做这个操作。
其实可以看出,这里的缓存只用到三个参数:组件的name
、tag
、实例componentInstance
,如果缓存整个vnode
,没有断开引用,会导致缓存无法释放。
网友评论