在刷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 源码对应起来就让我觉得别有收获