🪜 写在前面
缓存设计的好坏,直接决定前端系统的性能上限 与用户体验下限。
一个优秀的架构师(还不是没关系 先拥抱思想)需要理解并合理运用以下缓存层:
层级 | 作用 | 生命周期 |
---|---|---|
Memory 缓存 | 页面内高频数据复用 | 页面生命周期 |
localStorage/sessionStorage | 跨页面共享、快速读取 | 永久 / 会话 |
IndexedDB | 大体积、结构化数据 | 长期 |
接口缓存层 | 减少请求压力、接口回源 | 配置化 / TTL |
HTTP 缓存 | CDN/browser 缓存响应 | 浏览器策略控制 |
本篇将系统梳理前端所有可用缓存策略,并结合实战封装一个统一的缓存工具库 与请求缓存机制。
🧱 一、缓存分层模型图
javascript
┌──────────────────┐
│ IndexedDB │ ← 大体积 / 富媒体缓存
└────────┬─────────┘
│
┌─────────▼────────┐
│ localStorage/SS │ ← 跨页面持久化缓存
└─────────┬────────┘
│
┌───────────▼───────────┐
│ Memory Cache │ ← 当前页面状态共享
└───────────┬──────────┘
│
┌────────▼────────┐
│ 接口缓存层 │ ← 请求封装统一管理
└─────────────────┘
✅ 二、封装一个统一的缓存工具库
typescript
// cache/index.ts
export const cache = {
memory: new Map<string, any>(),
setLocal(key: string, value: any) {
localStorage.setItem(key, JSON.stringify(value))
},
getLocal<T>(key: string): T | null {
const raw = localStorage.getItem(key)
try {
return raw ? JSON.parse(raw) : null
} catch {
return null
}
},
setSession(key: string, value: any) {
sessionStorage.setItem(key, JSON.stringify(value))
},
getSession<T>(key: string): T | null {
const raw = sessionStorage.getItem(key)
try {
return raw ? JSON.parse(raw) : null
} catch {
return null
}
},
clear(key?: string) {
if (key) {
localStorage.removeItem(key)
sessionStorage.removeItem(key)
this.memory.delete(key)
} else {
localStorage.clear()
sessionStorage.clear()
this.memory.clear()
}
},
}
🔁 三、接口缓存封装(支持 TTL)
typescript
// cache/requestCache.ts
const requestCache = new Map<string, { data: any; expire: number }>()
export async function cachedRequest<T>(
key: string,
requestFn: () => Promise<T>,
ttl = 5000
): Promise<T> {
const now = Date.now()
const cacheItem = requestCache.get(key)
if (cacheItem && now < cacheItem.expire) {
return cacheItem.data
}
const data = await requestFn()
requestCache.set(key, { data, expire: now + ttl })
return data
}
使用方式:
csharp
const userInfo = await cachedRequest('userInfo', getUserInfo, 30000)
📦 四、应用场景实战示例
🎯 场景1:分页接口"翻页返回"不重复请求
javascript
const cacheKey = `goods?page=${page}&size=${size}`
const goodsList = await cachedRequest(cacheKey, () => getGoodsList({ page, size }), 60000)
🎯 场景2:配置类接口(如"角色列表"、"系统配置")
csharp
const roles = await cachedRequest('static_roles', fetchRoles, 5 * 60 * 1000)
🎯 场景3:首页推荐数据缓存(Memory + Fallback to Storage)
vbnet
const key = 'homepageRecommend'
let data = cache.memory.get(key) || cache.getLocal(key)
if (!data) {
data = await fetchHomepage()
cache.memory.set(key, data)
cache.setLocal(key, data)
}
🧠 五、IndexedDB 使用场景
场景 | 推荐使用 |
---|---|
大型资源文件(图片、视频、压缩包) | ✅ |
富文本内容离线保存 | ✅ |
文件上传分片缓存 | ✅ |
高并发结构化数据(日志流、埋点) | ✅ |
使用库推荐:
idb
(最简洁,Google 出品)localForage
(封装层丰富)- 自定义封装 indexedDB 的
store.open()
🔐 六、缓存安全与有效性策略
问题 | 建议方案 |
---|---|
多用户切换缓存污染 | 缓存 key 添加 userId 前缀 |
数据更新后缓存未同步 | 在接口响应中返回 version/hash 与本地对比 |
过期缓存未清除 | TTL 控制 + 定期清理任务 |
本地缓存中数据结构变动 | 版本标识 + 缓存结构迁移方案 |
⚡ 七、缓存与离线能力结合
可以扩展支持 PWA/Service Worker 缓存:
javascript
// sw.js
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return
event.respondWith(
caches.match(event.request).then((cachedRes) => {
return cachedRes || fetch(event.request).then((res) => {
const resClone = res.clone()
caches.open('my-cache').then((cache) => cache.put(event.request, resClone))
return res
})
})
)
})
📈 八、缓存监控与调试建议
-
Chrome DevTools → Application 面板查看 localStorage、sessionStorage、IndexedDB
-
添加缓存命中/写入 log(用于调试性能):
javascriptconsole.log(`[CACHE] HIT: ${key}`)
-
后端响应中返回
X-Cache-Status: HIT/MISS
-
开发环境中提供"清除缓存"按钮/快捷键
✅ 九、最佳实践清单
项目 | 建议 |
---|---|
接口级缓存 | 使用 cachedRequest 包装请求,设置合理 TTL |
静态配置缓存 | 使用 localStorage,搭配版本号判断是否更新 |
多页面共享数据 | 使用 sessionStorage,或 window.name 跨 iframe |
大文件缓存 | IndexedDB 并做清理策略 |
UI 状态缓存 | 使用 memory cache,组件卸载时清空 |
用户级缓存隔离 | 所有 key 添加 user_id 维度 |
🧠 总结一句话
缓存不是"为了省请求",而是"为了体验稳定、数据安全、系统弹性"。
下一篇我们进入「测试体系建设」阶段:
👉 《组件测试、集成测试与 E2E:前端测试体系构建》