「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。
keep-alive标签包裹的组件在切换时可以保留状态避免重新渲染。
但是缓存不能是无限的,否则会挤爆内存,有三种形式去进行缓存的淘汰。
1、 FIFO 队列。这个更适合做任务,不适合做组件的。
2、LFU。统计每个任务出现的次数,来淘汰出现次数较少的。缺点是比较耗费计算资源。
3、LRU缓存。全称Least Recently Used。也就是最近最少使用。
把所有元素按最近使用情况排序,比如已经到达固定存储量,添加新元素时需要判断当前缓存中是否存在相同元素,若有相同元素则将相同元素放到最后(更新),若无相同元素则添加该元素并删除第一个元素(也就是最早添加的)。
例子: 最大长度为4的缓存
当前缓存 | 添加元素 | 更新后缓存 |
---|---|---|
1,2,3,4 | 2 | 1,3,4,2 |
1,2,3 | 4 | 1,2,3,4 |
组件缓存用的最多的就是LRU缓存。(LRU Cache)
源码文件对应代码段
在Vue的源码文件中的packages/runtime-core/src/components/KeepAlive.ts中就有用到,采用的是set的实现方式。
setup函数
声明cache(Map)和keys(Set)
pruneCacheEntry(超出大小时,用于淘汰缓存)
若新增时超出缓存最大长度,需要用pruneCacheEntry删除keys中最早添加的key对应到cache中的数据。
在组件挂载或者更新时,向cache中新增数据
渲染函数(return ()=> {...})中为pendingCacheKey赋值
执行 render 的时候,pendingCacheKey 会被赋值为 vnode.key
在组件挂载或者更新(onMouted, onUpdated)时,向cache中新增 <key, vnode>
setup函数中return的渲染函数
获取子节点vnode和key
获取到当前keep-alive包裹的子节点
获取到当前节点的key值
通过cachedNode判断
通过cachedNode(cache中是否已经有这个key对应的vnode)来判断是否当前被组件是否已被缓存过。
- 若以被缓存过,则更新被缓存的节点到keys的末尾(更新)
- 若未被缓存过
- 向keys中添加当前node的key
- 判断是否超出缓存最大值,若超出缓存最大值则调用pruneCacheEntry传入最早add进keys的key作为参数,在pruneCacheEntry中cache根据入参的key来删除keys和cache对应数据。
总结:
可以看出是通过cache(map)和keys(set)配合实现了缓存功能。本质上可以说实际上是对keys实现了LRU算法,只是通过keys中的key确保cache中缓存数据没问题。
在setup函数返回的渲染函数中。
- 获取当前keep-alive的子节点以及key。
- 当缓存中存在与当前组件相同的key时,keys将该数据更新到末尾(将相同key的数据先删除后添加,达到更新的效果)。
- 若无相同key的节点已缓存,则直接向keys中add当前的key。同时需要判断是否超过缓存最大长度,若超过长度则通过pruneCacheEntry函数淘汰(删除)最早add进keys的key和该key在cache中对应的数据。
当组件挂载或更新(onMounted和onUpdated)时触发cacheSubtree,给cache添加当前key和vnode。
Map实现
- 通过has判断是否存在相同的数据(也就是判断当前节点和缓存过的节点是否相同)。
- 通过map.keys().next().value可以获取到第一位元素(这是由于map.keys()返回的是一个迭代器,可以通过next拿到第一个值)可以获取到map中最先放入的key。也就意味着可以通过delete这个key来淘汰最早set进去的key。
let cache = new Map()
cache.set('a',1)
cache.set('b',2)
cache.set('c',3)
console.log(cache.keys().next().value) // a 也就是最先放入的key
- set新的key是有序的,可以删除掉相同的key然后再次set达到将相同值更新到map末尾的效果。
满足实现LRU算法的条件。以下是leetcode算法题通过Map的实现。
LRU算法题
var LRUCache = function(capacity) {
this.cache = new Map()
// max赋值为缓存最大大小
this.max = capacity
};
LRUCache.prototype.get = function(key) {
if(this.cache.has(key)){
// 用临时变量存储键值
let tmp = this.cache.get(key)
// 删除原来的键和值,再次添加,达到更新到末尾的效果
this.cache.delete(key)
this.cache.set(key,tmp)
return tmp
}
return -1
};
LRUCache.prototype.put = function(key, value) {
if(this.cache.has(key)){
this.cache.delete(key)
// 当当前cache大小超出最大时需要有缓存淘汰机制
} else if(this.cache.size>=this.max) {
// 删除map头部元素,也就是最开始set的key
this.cache.delete(this.cache.keys().next().value)
}
this.cache.set(key,value)
return
};