
1. 重复请求缓存的核心价值前端开发中最让人头疼的问题之一就是重复请求——当用户频繁点击某个按钮或快速切换页面时系统往往会向服务器发送大量相同请求。这不仅浪费带宽还会导致服务器压力激增最终影响用户体验。我在电商项目中就遇到过这样的场景促销活动期间商品详情页的立即购买按钮被疯狂点击导致下单接口每秒被调用上百次服务器直接崩溃。重复请求缓存的本质是在客户端浏览器或移动端建立一道防洪堤对相同请求进行智能拦截和复用。举个例子当用户连续点击查询订单按钮时只有第一次点击会真实发送请求后续点击在缓存有效期内都会直接返回第一次的结果。这种机制能降低80%以上的冗余请求量对高并发场景尤为有效。2. 缓存策略深度解析2.1 浏览器原生缓存控制通过Request API的cache属性我们可以精细控制请求与浏览器缓存的交互方式// 强制绕过缓存适合实时性要求极高的场景 fetch(/api/data, { cache: no-store }) // 优先使用缓存包括过期缓存适合展示型数据 fetch(/api/news, { cache: force-cache }) // 典型用法先验证缓存有效性再决定是否请求 fetch(/api/user, { cache: no-cache, headers: { If-None-Match: etagValue } })但原生缓存存在明显局限无法针对业务逻辑定制如只缓存POST请求的特定参数组合缓存时间不可编程控制不同浏览器实现差异大2.2 内存级缓存方案对于需要精细控制的场景推荐使用内存缓存。以下是基于Map的简易实现class RequestCache { constructor(ttl 3000) { this.cache new Map() this.ttl ttl } async get(key, fetcher) { if (this.cache.has(key)) { const { data, timestamp } this.cache.get(key) if (Date.now() - timestamp this.ttl) { return data } } const freshData await fetcher() this.cache.set(key, { data: freshData, timestamp: Date.now() }) return freshData } } // 使用示例 const cache new RequestCache() const fetchUser () axios.get(/user/123) cache.get(user_123, fetchUser) // 实际发送请求 cache.get(user_123, fetchUser) // 3秒内返回缓存结果2.3 持久化缓存进阶对于SPA应用结合localStorage可以实现页面刷新不丢失的缓存class PersistentCache { constructor(namespace) { this.namespace namespace } async get(key, fetcher) { const cached localStorage.getItem(${this.namespace}:${key}) if (cached) { const { data, expire } JSON.parse(cached) if (expire Date.now()) { return data } } const freshData await fetcher() localStorage.setItem( ${this.namespace}:${key}, JSON.stringify({ data: freshData, expire: Date.now() 3600000 // 1小时过期 }) ) return freshData } }3. 实战中的缓存策略3.1 请求指纹生成精准识别重复请求的关键是生成唯一请求指纹。推荐以下算法function generateRequestKey(config) { return [ config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data) ].join(|) } // 示例GET /api/users?page1 和 GET /api/users?page2 会被识别为不同请求3.2 缓存失效策略常见的三种失效策略对比策略类型实现方式适用场景优缺点定时过期设置固定TTL数据变化频率固定实现简单但可能过早失效主动清除提供clearCache方法关键数据更新后精准控制但需手动触发版本号控制在URL中添加v1.0参数静态资源一劳永逸但需构建配合3.3 防抖与缓存的结合对于高频触发场景如搜索框输入建议结合防抖技术const searchCache new RequestCache() const debounceSearch _.debounce(async (keyword) { return searchCache.get(search_${keyword}, () { return axios.get(/search, { params: { q: keyword } }) }) }, 300)4. 性能优化实测数据在电商项目中的实测对比单位ms场景无缓存内存缓存本地存储缓存首屏加载12001200800二次访问11005020网络断开时失败失败可用内存占用低中高5. 常见问题解决方案5.1 缓存雪崩预防当大量缓存同时失效时会导致请求直接打到后端。解决方案// 在基础TTL上增加随机抖动 const ttl 3000 Math.random() * 20005.2 敏感数据隔离对于不同安全等级的数据应该使用独立的缓存实例const publicCache new RequestCache() // 公共数据 const privateCache new RequestCache() // 用户私有数据5.3 移动端优化技巧在React Native中建议将缓存与AsyncStorage结合import { AsyncStorage } from react-native class RNRequestCache { async get(key, fetcher) { try { const cached await AsyncStorage.getItem(key) if (cached) { /* 处理逻辑 */ } } catch (e) { // 存储失败时降级为普通请求 return fetcher() } } }6. 高级应用场景6.1 GraphQL请求缓存针对GraphQL的特性需要特殊处理查询指纹function generateGraphQLKey(query, variables) { return hashString(JSON.stringify({ query: minifyQuery(query), variables })) }6.2 文件上传缓存对大文件上传实现断点续传const uploadCache { setProgress(fileHash, progress) { localStorage.setItem(upload_${fileHash}, progress) }, getProgress(fileHash) { return localStorage.getItem(upload_${fileHash}) } }6.3 Service Worker集成通过workbox实现更底层的缓存控制import { CacheFirst } from workbox-strategies registerRoute( /api/products, new CacheFirst({ cacheName: api-cache, plugins: [/* 自定义插件 */] }) )在实现缓存方案时一定要根据业务特点选择合适策略。我曾在一个金融项目中过度依赖本地缓存导致用户看到陈旧的账户余额。最终我们采用分层缓存方案基础信息缓存1小时资金相关数据强制实时请求。记住缓存是手段而非目的永远要以业务正确性为前提。