LRU算法理解keep-alive原理

245 阅读2分钟

概念

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
接收三个值

  • include 只有名称匹配的组件才能被缓存
  • exclude 名称匹配的组件不能被缓存
  • max 最大缓存实例数
<!-- 以英文逗号分隔的字符串 --> 
<KeepAlive include="a,b"> 
    <component :is="view" /> 
</KeepAlive> 
<!-- 正则表达式 (需使用 `v-bind`) --> 
<KeepAlive :include="/a|b/"> 
    <component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) --> 
<KeepAlive :include="['a', 'b']">
    <component :is="view" />
</KeepAlive>

作用

主要用于保留组件状态或避免重新渲染。

原理

keep-alive运用了 LRU 算法,来实现组件缓存,其原理如下

  1. 获取keep-alive标签里的第一个子元素

  2. 如果子元素的name不在inclue或者在exluede,则直接返回vnode,否则进行缓存

  3. 根据组件的id和tag生成key,如果缓存对象中有对应key的实例,则通过LRU算法实现实例的位置置换

  4. 若缓存对象中没有对应key的实例,则会判断是否超出max值,超出则删除最久未使用的实例

LRU 算法

LRU是 Least Recently Used 的缩写,即最近最少使用
当缓存使用的空间达到上限后,就需要从已有的数据中淘汰一部分以维持缓存的可用性,而淘汰数据的选择就是通过LRU算法完成的。
它的核心思想是当缓存满时,会优先淘汰那些最近最少使用的缓存对象。

借助其他大佬画的图,加深理解一下,示例设置缓存大小为3,依次访问页面

image.png

通过上图的理解,可以得出LRU算法的关键的四个点

  1. 有限的存储空间
  2. 顺序的存储结构(前端类似的有Map/Array)
  3. 最近使用的元素置于栈顶
  4. 栈满则删除栈底元素(栈底元素就代表最近最少使用)

源码解析

展示keep-alive的主要代码

  render () {
    const slot = this.$slots.default
    const vnode = getFirstComponentChild(slot)
    // 判断vnode是否是组件
    const componentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      const name = getComponentName(componentOptions)
      const { include, exclude } = this
      // 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
       // 根据组件ID和tag生成缓存Key
      const key:  = vnode.key == null
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key
      // 如果缓存对象中存在该key对象,则将其置顶
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        remove(keys, key)
        keys.push(key)
      } else {
        cache[key] = vnode;
        keys.push(key);
        // 检查缓存的实例数量是否超过max设置值,超过则根据LRU置换策略删除最近最久未使用的实例
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }

      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }