前言
假设一个手机后台应用:我们同时打开了十几个App,但手机内存毕竟是有限的,那么系统就需要决定哪些App能保持活跃状态,哪些该被关闭以腾出空间。LRU(Least Recently Used)算法就是干这活的,例如地图10分钟前用过,那么它就会被留着;视频已经半天没碰了,就会被收起来。在Vue项目中,我们也可以通过LRU,来提升应用的流畅度。
本文将用3个真实场景,初步介绍LRU的使用方法。
一、Vue项目中的缓存痛点
先看三个典型问题:
- 商品列表页 -> 详情页 -> 返回列表页刷新:用户刚浏览的位置丢失,需要重新查找,体验糟糕。
- 复杂表单切换标签页后数据丢失:用户填写了一半的数据消失,开发人员需要处理由此引发的逻辑问题。
- 重复加载相同图片造成流量浪费:尤其在移动端或图片较多的场景,不必要的流量消耗直接影响成本和性能。
这些问题的核心,都是如何高效管理有限的内存资源? LRU算法正是解决以上这些问题的经典策略。
二、LRU在Vue中的实践
场景1:路由缓存优化(keep-alive 进阶版)
基础方案痛点:
<keep-alive>
<router-view/>
</keep-alive>
此方案会缓存所有路由组件,可能导致内存占用持续增长,最终影响性能。
基于LRU的优化方案 (Vue 2 示例):
<script>
export default {
data() {
return {
cacheKeys: [], // 当前缓存的组件key队列
maxCache: 5 // 最大缓存数量
}
},
render() {
return (
<keep-alive include={this.cacheKeys}>
<router-view key={this.$route.fullPath} />
</keep-alive>
)
},
activated() {
this.updateCache(this.$route)
},
methods: {
updateCache(route) {
const key = route.fullPath
const index = this.cacheKeys.indexOf(key)
// LRU缓存更新策略
if (index > -1) {
this.cacheKeys.splice(index, 1) // 移除旧位置
} else if (this.cacheKeys.length >= this.maxCache) {
this.cacheKeys.shift() // 移除最久未使用的
}
this.cacheKeys.push(key) // 添加到最新位置
}
}
}
</script>
场景2:图片懒加载 + LRU缓存
<template>
<img :src="realSrc" v-lazy="url" /> <!-- 使用自定义指令 v-lazy -->
</template>
<script>
import { LRUCache } from './lru';
const imageCache = new LRUCache(20); // 缓存最近使用的20张图片
export default {
directives: {
lazy: {
mounted(el, { value: url }) {
const observer = new IntersectionObserver(([{ isIntersecting }]) => {
if (isIntersecting) {
// 图片进入视口
if (imageCache.has(url)) {
// 命中缓存,直接使用
el.src = imageCache.get(url);
} else {
// 未命中,加载图片
const img = new Image();
img.src = url;
img.onload = () => {
// 加载成功,存入缓存
imageCache.set(url, url);
el.src = url;
};
}
observer.unobserve(el); // 停止观察
}
});
observer.observe(el); // 开始观察元素
}
}
}
}
</script>
场景3:API响应缓存(Axios拦截器方案)
// utils/request.js
import LRUCache from './lru';
const apiCache = new LRUCache(50); // 缓存50个GET接口响应
export function createRequest() {
const service = axios.create();
// 请求拦截:检查缓存
service.interceptors.request.use(config => {
if (config.method === 'get' && config.cache) {
const cacheKey = generateKey(config); // 根据请求参数生成唯一key
if (apiCache.has(cacheKey)) {
// 命中缓存,直接返回缓存数据,避免真实请求
return Promise.resolve(apiCache.get(cacheKey));
}
config.cacheKey = cacheKey; // 标记此请求需要缓存
}
return config;
});
// 响应拦截:存储缓存
service.interceptors.response.use(response => {
const { config, data } = response;
if (config.cacheKey) {
apiCache.set(config.cacheKey, data); // 成功响应后存入缓存
}
return data;
});
return service;
}
// 使用示例
const request = createRequest();
request.get('/api/goods', { cache: true }); // 第二次调用相同请求将直接读取缓存
三、Vue专用的响应式LRU Hook
// lib/lru.js
import { reactive, toRefs } from 'vue';
export function useLRU(capacity = 10) {
// 使用Vue的响应式系统管理状态
const state = reactive({
cacheMap: new Map(), // 存储键值对
keys: [] // 存储键的顺序(最近使用->最久未用)
});
const get = (key) => {
if (!state.cacheMap.has(key)) return null;
// 更新使用顺序:将key移到数组末尾(最近使用)
state.keys = state.keys.filter(k => k !== key);
state.keys.push(key);
return state.cacheMap.get(key);
};
const set = (key, value) => {
if (state.cacheMap.has(key)) {
// Key已存在:先移除旧位置
state.keys = state.keys.filter(k => k !== key);
} else {
// Key不存在:检查容量
if (state.keys.length >= capacity) {
const oldestKey = state.keys.shift(); // 移除最久未使用的key
state.cacheMap.delete(oldestKey);
}
}
// 添加/更新键值对,并记录在keys末尾
state.keys.push(key);
state.cacheMap.set(key, value);
};
return {
...toRefs(state), // 转换为refs以便在模板中使用
get,
set
};
}
// 在组件中使用
import { useLRU } from './lib/lru';
export default {
setup() {
const { cacheMap, keys, get, set } = useLRU(5);
const getData = async (key) => {
const cached = get(key);
if (cached) return cached;
// 模拟异步获取数据
const data = await fetchData(key);
set(key, data);
return data;
};
return {
cacheMap,
keys,
getData
};
}
}
小结
通过在Vue项目中的在路由缓存、图片懒加载和 API 响应这几个应用LRU的示例场景,咱们大致能够了解到这个算法主要是做什么的,简而言之——“让常用功能保持活跃,冷门功能适时释放”