keep-alive 与 LRU 算法
- keep-alive 的实现原理是什么
- 与 keep-alive 相关的生命周期函数是什么,什么场景下会进行使用
- keep-alive 的常用属性有哪些
keep-alive 组件是 vue 的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive 内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。
keep-alive 具有 include 和 exclude 属性,通过它们可以控制哪些组件进入缓存。另外它还提供了 max 属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存。
受 keep-alive 的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是 activated 和 deactivated,它们分别在组件激活和失活时触发。第一次 activated 触发是在 mounted 之后
在具体的实现上,keep-alive 在内部维护了一个 key 数组和一个缓存对象
key 数组记录目前缓存的组件 key 值,如果组件没有指定 key 值,则会为其自动生成一个唯一的 key 值
cache 对象以 key 值为键,vnode 为值,用于缓存组件对应的虚拟 DOM
在 keep-alive 的渲染函数中,其基本逻辑是判断当前渲染的 vnode 是否有对应的缓存,如果有,从缓存中读取到对应的组件实例;如果没有则将其缓存。
当缓存数量超过 max 数值时,keep-alive 会移除掉 key 数组的第一个元素。
// keep-alive 内部的声明周期函数
created () {
this.cache = Object.create(null)
this.keys = []
}
keep-alive 中的生命周期哪些
keep-alive 是 Vue 提供的一个内置组件,用来对组件进行缓存——在组件切换过程中将状态保留在内存中,防止重复渲染 DOM。
如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。
当组件被换掉时,会被缓存到内存中、触发 deactivated 生命周期;当组件被切回来时,再去缓存里找这个组件、触发 activated 钩子函数。
LRU 缓存算法
大致题意
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。 实现 LRUCache 类:
- LRUCache(int capacity) 以
正整数作为容量 capacity 初始化 LRU 缓存 - int get(int key)
如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1。 - void put(int key, int value)
如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
题目要求的1和2相对简单,主要是条件3,当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值。容量和条件1相呼应,关键是怎么理解最久未使用呢?
- 读和写都是在使用数据
- 假设不管是读还是写,我们都把对应的
key值放到数组的末尾,那么是不是意味着数组的头部就是最久未使用的了呢?
数组&&对象实现方式
var LRUCache = function (capacity) {
// 用数组记录读和写的顺序
this.keys = []
// 用对象来保存key value值
this.cache = {}
// 容量
this.capacity = capacity
}
LRUCache.prototype.get = function (key) {
// 如果存在
if (this.cache[key]) {
// 先删除原来的位置
remove(this.keys, key)
// 再移动到最后一个,以保持最新访问
this.keys.push(key)
// 返回值
return this.cache[key]
}
return -1
}
LRUCache.prototype.put = function (key, value) {
if (this.cache[key]) {
// 存在的时候先更新值
this.cache[key] = value
// 再更新位置到最后一个
remove(this.keys, key)
this.keys.push(key)
} else {
// 不存在的时候加入
this.keys.push(key)
this.cache[key] = value
// 容量如果超过了最大值,则删除最久未使用的(也就是数组中的第一个key)
if (this.keys.length > this.capacity) {
removeCache(this.cache, this.keys, this.keys[0])
}
}
}
// 移出数组中的key
function remove(arr, key) {
if (arr.length) {
const index = arr.indexOf(key)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
// 移除缓存中 key
function removeCache(cache, keys, key) {
cache[key] = null
remove(keys, key)
}
Map实现方式
第一种实现方式,我们借助了数组来存储每次key被访问(get、set)的顺序,这样实现比较麻烦一些,有没有其他方案,让我们更加便捷一些,不需要额外维护数组呢?借助
Map设置值时可以保持顺序性,处理LRU算法将会及其方便
/**
* @param {number} capacity
*/
var LRUCache = function (capacity) {
this.cache = new Map()
this.capacity = capacity
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function (key) {
if (this.cache.has(key)) {
const value = this.cache.get(key)
// 更新位置
this.cache.delete(key)
this.cache.set(key, value)
return value
}
return -1
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function (key, value) {
// 已经存在的情况下,更新其位置到”最新“即可
// 先删除,后插入
if (this.cache.has(key)) {
this.cache.delete(key)
} else {
// 插入数据前先判断,size是否符合capacity
// 已经>=capacity,需要把最开始插入的数据删除掉
// keys()方法得到一个可遍历对象,执行next()拿到一个形如{ value: 'xxx', done: false }的对象
if (this.cache.size >= this.capacity) {
this.cache.delete(this.cache.keys().next().value)
}
}
this.cache.set(key, value)
};