Note:Vue2 Keep-alive原理

请说一下keep-alive的原理。

keep-alive,在组件切换过程中会将包裹的组件实例的状态保留在内存中,防止重复渲染DOM。keep-alive有一个abstract属性为true,确保其不会渲染成DOM元素。在created阶段,初始化一个cache对象、keys数组,cache用来存缓存组件的虚拟dom及其缓存key值,keys用来存缓存组件的key值。在mounted阶段实时监听include、exclude这两个的变化,并执行相应的缓存操作。keep-alive是调用render函数来执行缓存操作的,它会获取到keep-alive包裹的第一个组件以及它的组件名称,判断此组件名称是否被白名单、黑名单匹配,如果没有被白名单匹配 或者 被黑名单匹配,则直接返回VNode,不进行缓存。否则会判断组件key是否存在,如果不存在会根据组件ID、tag生成缓存唯一key值,并在缓存集合cache中查找是否已缓存过此组件。如果已缓存过,直接取出缓存组件,并更新缓存key在keys中的位置,将该组件的key重新插入的keys数组的末尾。如果没缓存过,则分别在cache、keys中保存此组件以及他的缓存key,并检查数量是否超过max,如果超过则删除最久没有被访问的组件,也就是缓存中的第一个。

// <keep-alive> 组件的实现也是一个对象
export default {
  name: 'keep-alive',
  // 抽象组件
  abstract: true,

  props: {
    // 只有名称匹配的组件才会被缓存
    include: patternTypes,
    // 任何名称匹配的组件都不会被缓存
    exclude: patternTypes,
    // 缓存组件的最大数量, 因为我们缓存的是vnode对象,它也会持有DOM,当我们缓存很多的时候,会比较占用内存,所以该配置允许我们指定缓存大小
    max: [String, Number]
  },

  created () {
    // 初始化存储缓存的cache对象和缓存 vNode 键的数组
    this.cache = Object.create(null)
    this.keys = []
  },

  //  destroyed 中销毁所有cache中的组件实例
  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    // 监听 include 和 exclude的变化,在变化的时候重新调整 cache的内容
    // 其实就是对 cache 做遍历,发现缓存的节点名称和新的规则没有匹配上的时候,就把这个缓存节点从缓存中摘除
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },
  // 自定义render函数
  render () {
    /*
    * 获取第一个子元素的 vnode
    * 由于我们也是在 <keep-alive> 标签内部写 DOM,所以可以先获取到它的默认插槽,然后再获取到它的第一个子节点。<keep-alive> 只处理第一个子元素,所以一般和它搭配使用
    * 的有 component 动态组件或者是 router-view,这点要牢记。
    */
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot)

    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      // 判断当前组件名称和 include、exclude 的关系:
      const name: ?string = getComponentName(componentOptions)
      const { include, exclude } = this

      // matches就是做匹配,分别处理了数组、字符串、正则表达式的情况
      // 组件名如果满足了配置 include 且不匹配或者是配置了 exclude 且匹配,那么就直接返回这个组件的 vnode,否则的话走下一步缓存:
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      const key: ?string = vnode.key == null
        // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
      // 如果命中缓存,则直接从缓存中拿 vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        // 使用 LRU 缓存策略,把key移除,同时加在最后面
        remove(keys, key)
        keys.push(key)
      } else {
        // 没有命中缓存,则把 vnode设置进缓存
        cache[key] = vnode
        keys.push(key)
        // prune oldest entry
        // 配置了max 并且缓存的长度超过了 this.max,则要从缓存中删除第一个
        if (this.max && keys.length > parseInt(this.max)) {
          // 除了从缓存中删除外,还要判断如果要删除的缓存并的组件 tag 不是当前渲染组件 tag,也执行删除缓存的组件实例的 $destroy 方法。
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }
      // keepAlive标记位
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容