LRU算法在Vue內置組件keep-alive中的使用

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算法的資料請關註WalkonNet其它相關文章!

推薦閱讀: