vue keep-alive 内置组件与 LRU缓存机制

    技术2022-07-10  115

    在刷leetcode 的时候,遇到了一道题 LRU缓存机制 ,和vue keep-alive 里的缓存机制是一样的,就单独把这个拿出来,然后 顺便 把 keep-alive 学习一遍

    1、Keep-alive组件

    function pruneCacheEntry ( cache: VNodeCache, key: string, keys: Array<string>, current?: VNode ) { // 把过期了的 vnode 给销毁了 // 如果当前 这个 vnode 正在展示当中,也就是 current ,那就不销毁 const cached = cache[key] if (cached && (!current || cached.tag !== current.tag)) { cached.componentInstance.$destroy() } // 但是还是 手动 把 这个节点给 清空,方便后期 浏览器回收内存 cache[key] = null // 删除 keys 数组中的 key remove(keys, key) } export default { name: 'keep-alive', abstract: true, // 接受的 三个参数 props: { include: patternTypes, exclude: patternTypes, max: [String, Number] }, created () { // 创建内部变量 // 在 注意,这两个变量不是响应式的,只是简单地挂在了 当前 vue 实例上面 this.cache = Object.create(null) this.keys = [] }, destroyed () { // 当 当前组件销毁的时候,把所有的 缓存清除 for (const key in this.cache) { pruneCacheEntry(this.cache, key, this.keys) } }, mounted () { // 监听 include exclude 这两个 props,然后动态的删除修改缓存的数据 this.$watch('include', val => { pruneCache(this, name => matches(val, name)) }) this.$watch('exclude', val => { pruneCache(this, name => !matches(val, name)) }) }, // 这里使用的就是函数式组件,没有 template 的那种 // 这里返回的是一个 vnode 节点,而 $slot 就是一个 vnode 节点 render () { // 这里就是通过 $slot 获得 的 需要缓存的 组件 const slot = this.$slots.default // 从 其中 获取第一个进行缓存,如上文 所示,default 是一个 数组,从这个数组里面拿到第一个 const vnode: VNode = getFirstComponentChild(slot) const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (componentOptions) { // 获取 缓存的组件名称 const name: ?string = getComponentName(componentOptions) const { include, exclude } = this // 如果 不再 included 中,或者在 exclude 之内,就不执行缓存 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 ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key // keep-alive 的内置属性之一, cache 和 keys if (cache[key]) { vnode.componentInstance = cache[key].componentInstance // make current key freshest // 这里的作用其实也很简单,就是 将 最新使用的节点 名称 放到 缓存 keys 数组的最前面 // 这样就可以做到 当 设置了 最多缓存的组件之后,超出 则删除 最久没使用的节点 remove(keys, key) keys.push(key) } else { // 这里 缓存了 当前 vue 页面的 实例, cache[key] = vnode keys.push(key) // 删除最老的节点 if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) } } vnode.data.keepAlive = true } return vnode || (slot && slot[0]) } } 创建内部变量 keys 数组 和 cache 对象 (这里就是LRU缓存机制) 的关键通过 $slot 获取 注入的组件,并且只获取 第一个默认组件inculde 或者 exclude 存在的情况下,匹配 inculde 参数 和 exclude 参数,如果 不再 include 或者 在 enclude 中,不进行缓存获取节点名称,或者 更具节点 cid 等信息拼出当前 组件名称

    使用 LRU 缓存机制进行缓存,并更具 max 参数,删除 最久没访问的节点

    2、LRU 缓存 leetcode 

     

    /** * @param {number} capacity */ var LRUCache = function(capacity) { // 和 上面的代码一样,一个缓存 对象,一个数组,一个 最大值 this.capacity = capacity; this.cache = {} this.cacheArr = [] }; /** * @param {number} key * @return {number} */ LRUCache.prototype.get = function(key) { // 获取的时候,如果当前 有缓存,那么把缓存拿出来,并且把 当前的 key 拿到最前面 const index = this.cacheArr.indexOf(key) if (index !== -1) { this.cacheArr.splice(index, 1) this.cacheArr.unshift(key) } return this.cache[key] || -1 }; /** * @param {number} key * @param {number} value * @return {void} */ LRUCache.prototype.put = function(key, value) { // 存入 数据 this.cache[key] = value // 如果 缓存的数组中存在,说明以前已经缓存过了 // 就把 当前的 key 放到最前面,变成最新鲜的 const index = this.cacheArr.indexOf(key) if (index !== -1) { this.cacheArr.splice(index, 1) } this.cacheArr.unshift(key) // 同时检验 是否超出了最大长度,超出了的话,把数据清除 if (this.cacheArr.length > this.capacity) { const key = this.cacheArr.slice(-1)[0] this.cache[key] = null this.cacheArr.pop() } }; /** * Your LRUCache object will be instantiated and called as such: * var obj = new LRUCache(capacity) * var param_1 = obj.get(key) * obj.put(key,value) */ 这里的思路就是使用了 keep-alive 组件里面的缓存方式创建一个 缓存的对象,用来进行存储创建一个存放缓存 key 的数组,用来校验当前 存放数据的 ‘新鲜度’当 有新数据,或者 数据被读取了之后,把 对应的 key 放到 数组 的最前面,变成最新鲜的,所以只需要维持一个 key 的数组就能知道当前数据的新鲜程度查看 当前 keys 数组的长度来检查是否超出缓存的长度,超出了,删除 key 和对应的缓存

     

    最后从结果上来看,我的实现显然不是最优解,但是 可以和 vue 源码对应起来就让我觉得别有收获

     

    Processed: 0.017, SQL: 9