@IT·互联网LOVETOO-前端编程

前端vue项目内存泄漏排查总结

2022-06-10  本文已影响0人  车文烨

前端vue项目内存泄漏排查总结

未完待续。。。

背景

  1. 项目由vue-element-admin改造,二次封装了element-ui,组件套的有点深;
  2. 系统上线一段时间后,业务频繁反馈页面崩溃;
  3. 项目使用了页面缓存,移除的话没问题,但因业务需要不能移除:
    <transition name="fade-transform" mode="out-in">
      <keep-alive :include="cachedViews">
        <!-- 匹配三级路由,并缓存 -->
        <router-view :key="key" />
      </keep-alive>
    </transition>
    

排查

  1. 使用谷歌开发工具面板performancememory排查,打快照,发现详情页内存一直累积,无法释放;
  2. 列表页和详情页引用了公共组件(注意import引入的资源在内存中实质上是同一个对象);
  3. memory排查分析分析累积的都是些vue组件;
  4. 注意组件的name与路由的name保持一致且唯一,不然用keep-alive无法正常缓存(通过componentOptions.Ctor.options.name || componentOptions.tag决定include包含的组件缓存);

解决方案

  1. 缓存的页面走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>
  1. 比较粗暴的方法:主动销毁组件,详情页离开(销毁)之前,递归把组件的componentInstanceelm.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)
  }
}
  1. vuevue-template-compiler都升级到2.6.13版本;

但有个情况依然有问题,就是两个不同列表(缓存)的详情页(不缓存)之间切换,详情页引用同一个详情组件的时候内存一样不释放。

issues commit
issues 源码改动

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.13mountedupdated都执行了this.cacheVNode()

这里this.vnodeToCache = null,把vnode的引用断开了!而旧版本的cache[key] = vnode是没有做这个操作。

其实可以看出,这里的缓存只用到三个参数:组件的nametag、实例componentInstance,如果缓存整个vnode,没有断开引用,会导致缓存无法释放。

上一篇 下一篇

猜你喜欢

热点阅读