LRU算法在Vue内置组件keep-alive中的使用

 更新时间:2021年05月31日 17:30:08   作者:Cc冲冲冲  
LRU算法全称为least recently use 最近最少使用,核心思路是最近被访问的以后被访问的概率会变高,那么可以把之前没被访问的进行删除,维持一个稳定的最大容量值,从而不会导致内存溢出。

vue的keep-alive内置组件的使用也是使用了改算法,源码如下:

export default {
  name: "keep-alive",
  // 抽象组件属性 ,它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle 的过程中
  abstract: true, 
  props: {
    // 被缓存组件
    include: patternTypes, 
    // 不被缓存组件
    exclude: patternTypes,
    // 指定缓存大小
    max: [String, Number] 
  },
  created() {
    // 初始化用于存储缓存的 cache 对象
    this.cache = Object.create(null);
    // 初始化用于存储VNode key值的 keys 数组
    this.keys = []; 
  },
  destroyed() {
    for (const key in this.cache) {
      // 删除所有缓存
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },
  mounted() {
    // 监听缓存(include)/不缓存(exclude)组件的变化
    // 在变化时,重新调整 cache
    // pruneCache:遍历 cache,如果缓存的节点名称与传入的规则没有匹配上的话,就把这个节点从缓存中移除
    this.$watch("include", val => {
      pruneCache(this, name => matches(val, name));
    });
    this.$watch("exclude", val => {
      pruneCache(this, name => !matches(val, name));
    });
  },
  render() {
    // 获取第一个子元素的 vnode
    const slot = this.$slots.default;
    const vnode: VNode = getFirstComponentChild(slot);
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions;
    if (componentOptions) {
      // name 不在 inlcude 中或者在 exlude 中则直接返回 vnode,否则继续进行下一步
      // check pattern
      const name: ?string = getComponentName(componentOptions);
      const { include, exclude } = this;
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode;
      }
      
      const { cache, keys } = this;
      // 获取键,优先获取组件的 name 字段,否则是组件的 tag
      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;
        
      // --------------------------------------------------
      // 下面就是 LRU 算法了,
      // 如果在缓存里有则调整,
      // 没有则放入(长度超过 max,则淘汰最近没有访问的)
      // --------------------------------------------------
      // 如果命中缓存,则从缓存中获取 vnode 的组件实例,并且调整 key 的顺序放入 keys 数组的末尾
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key);
        keys.push(key);
      }
      // 如果没有命中缓存,就把 vnode 放进缓存
      else {
        cache[key] = vnode;
        keys.push(key);
        // prune oldest entry
        // 如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      
      // keepAlive标记位
      vnode.data.keepAlive = true;
    }
    return vnode || (slot && slot[0]);
  }
};

// 移除 key 缓存
function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key]
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}

// remove 方法(shared/util.js)
/**
 * Remove an item from an array.
 */
export function remove (arr: Array<any>, item: any): Array<any> | void {
  if (arr.length) {
    const index = arr.indexOf(item)
    if (index > -1) {
      return arr.splice(index, 1)
    }
  }
}

实现一个自己的LRU算法

lru算法 的核心api(put get)和一个size最大容器值,本质是类似队列 put实现思路 1 是否存在,存在就先删除,再添加到队头 2 不存在,容量是否满了,删除最后一个队尾,再添加队头 get实现思路: 1.有就返回,同时插入队头 2.没有返回-1 时间复杂度O(1)

class LRU {
  constructor(size) {
    this.cache = new Map()
    this.size = size
  }
  put (key, val) {
    //存在
    if (this.cache.has(key)) {
      //删除
      this.cache.delete(key)
    } else {
      //不存在,容量是否满了
      if (this.size === this.cache.size) {
        //删除最后一个
        this.cache.delete(this.cache.keys().next().value) //拿到队尾的元素
      }
    }
    //插在队头
    this.cache.set(key, val)
  }
  get (key) {
    let val = this.cache.get(key)
    if (!val) {
      return -1
    }
    //访问了就需要放在队头
    this.put(key, val)
    return val
  }
}

另一种

//定义节点类
class Node {
    constructor(pre, next, value, key){
        this.pre = pre;
        this.next = next;
        this.value = value;
        this.key = key;
    }
}

//定义双向链表
class DoubleList {
    constructor(head, tail){
        this.head = head;
        this.tail = tail;
    }
}


class LRUCache {
    //构造函数,传入缓存容量
    constructor(max){
        this.max = max;
        this.map = new Map();
        let node = new Node(null, null, null, null);
        this.doubleList = new DoubleList(node, node);
    }
    
    /**
     * 获取缓存值
     * 不存在返回-1,存在返回对应value值,并将此节点移到尾巴
     * @param {*} key  key值
     */
    get(key){
        let node = this.map.get(key)
        if(!node){
            return -1;
        }else{
            this.moveNode2Tail(key, node);
            return node.value;
        }
    }

    /**
     * 插入缓存
     * 1.不存在对应key值,加到尾巴
     * 2.存在对应key值,更新此key值对应value并提到尾巴
     * 3.超出容量的话,去掉头部数据
     * @param {*} key  key值
     * @param {*} value  value
     */
    put(key, value) {
        let node = this.map.get(key);
        if(node){
            if(!node.next){
                node.value = value;
                return;
            }
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }
        let newNode = new Node(null, null, value, key);
        newNode.pre = this.doubleList.tail;
        this.doubleList.tail.next = newNode;
        this.doubleList.tail = newNode;
        this.map.set(key, newNode);
        if(this.map.size > this.max){
            this.map.delete(this.doubleList.head.next.key);
            this.doubleList.head.next = this.doubleList.head.next.next;
            this.doubleList.head.next.pre = this.doubleList.head;          
        }
    }
    
    //将节点移到尾巴
    moveNode2Tail(key,node){   
        if(!node.next){
            return;
        }
        //删除节点   
        node.pre.next = node.next;
        node.next.pre = node.pre;
        this.map.delete(key)
        //新增尾巴节点
        let newNode = new Node(null, null, node.value, key);
        newNode.pre = this.doubleList.tail;
        this.doubleList.tail.next = newNode;
        this.doubleList.tail = newNode;
        this.map.set(key, newNode);
    }
}

以上就是LRU算法在Vue内置组件keep-alive中的使用的详细内容,更多关于Vue LRU算法的资料请关注脚本之家其它相关文章!

相关文章

  • Vue实现开关按钮拖拽效果

    Vue实现开关按钮拖拽效果

    这篇文章主要为大家详细介绍了Vue实现开关按钮拖拽效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-09-09
  • webstorm+vue初始化项目的方法

    webstorm+vue初始化项目的方法

    今天小编就为大家分享一篇webstorm+vue初始化项目的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-10-10
  • Vue3实现折叠面板组件的示例代码

    Vue3实现折叠面板组件的示例代码

    折叠面板大家都不陌生,很多时候需要实现一些复杂的交互,就会用到它,简洁直观还美观,下面就跟随小编一起学习一下如果使用Vue3实现折叠面板组件吧
    2024-01-01
  • Vue Autocomplete 自动完成功能简单示例

    Vue Autocomplete 自动完成功能简单示例

    这篇文章主要介绍了Vue Autocomplete 自动完成功能,结合简单示例形式分析了Vue使用el-autocomplete组件实现自动完成功能相关操作技巧,需要的朋友可以参考下
    2019-05-05
  • Vue2.x通用条件搜索组件的封装及应用详解

    Vue2.x通用条件搜索组件的封装及应用详解

    这篇文章主要为大家详细介绍了Vue2.x通用条件搜索组件的封装及应用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • 详解vue中使用transition和animation的实例代码

    详解vue中使用transition和animation的实例代码

    这篇文章主要介绍了详解vue中使用transition和animation的实例代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • vue3 HTTP请求中的axios示例详解

    vue3 HTTP请求中的axios示例详解

    Axios 是一个简单的基于 promise 的 HTTP 客户端,适用于浏览器和 node.js。Axios 在具有非常可扩展的接口的小包中提供了一个简单易用的库,这篇文章主要介绍了vue3-HTTP请求之axios,需要的朋友可以参考下
    2022-12-12
  • 用Vue-cli搭建的项目中引入css报错的原因分析

    用Vue-cli搭建的项目中引入css报错的原因分析

    本篇文章主要介绍了用Vue-cli搭建的项目中引入css报错的原因分析,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • Vue路由重复点击报错问题及解决

    Vue路由重复点击报错问题及解决

    这篇文章主要介绍了Vue路由重复点击报错问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • 使用element组件table表格实现某条件下复选框无法勾选

    使用element组件table表格实现某条件下复选框无法勾选

    这篇文章主要介绍了使用element组件table表格实现某条件下复选框无法勾选问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03

最新评论