悠悠楠杉
JavaScript请求缓存实战:提升性能的五大策略
JavaScript请求缓存实战:提升性能的五大策略
一、理解请求缓存的本质
前端开发中,频繁的网络请求会导致性能瓶颈。我曾参与过一个电商项目,商品列表页的API请求在没有缓存策略时,平均加载时间达到3.2秒。通过实现请求缓存后,这个数字降到了1.4秒——这让我深刻认识到缓存的重要性。
请求缓存的核心思想是:当相同请求再次发生时,优先使用本地存储的响应数据。与浏览器默认缓存不同,我们需要实现的是应用层缓存控制,这给了开发者更大的灵活性。
二、基础实现方案
javascript
const cacheMap = new Map();
async function cachedFetch(url, options = {}) {
const cacheKey = ${url}_${JSON.stringify(options)}
;
if (cacheMap.has(cacheKey)) {
console.log('[缓存命中]', cacheKey);
return Promise.resolve(cacheMap.get(cacheKey));
}
try {
const response = await fetch(url, options);
const data = await response.json();
cacheMap.set(cacheKey, data);
console.log('[新缓存存入]', cacheKey);
return data;
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
这个基础版本存在三个明显问题:
1. 内存泄漏风险(缓存无限增长)
2. 缺乏缓存过期机制
3. 不同参数的同API请求会被视为不同请求
三、生产级缓存方案
我们需要更健壮的实现:
javascript
class RequestCache {
constructor(maxSize = 50, ttl = 60000) {
this.cache = new Map();
this.maxSize = maxSize;
this.ttl = ttl; // 默认1分钟
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
// 检查过期时间
if (Date.now() > entry.expireAt) {
this.cache.delete(key);
return null;
}
return entry.data;
}
set(key, data) {
// 执行LRU淘汰策略
if (this.cache.size >= this.maxSize) {
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
this.cache.set(key, {
data,
expireAt: Date.now() + this.ttl
});
}
clear() {
this.cache.clear();
}
}
四、缓存策略进阶技巧
- 请求去重:当相同请求已经在进行时,不再发起新请求
javascript
const pendingRequests = new Map();
async function dedupedFetch(url) {
if (pendingRequests.has(url)) {
return pendingRequests.get(url);
}
const promise = fetch(url).then(res => res.json())
.finally(() => pendingRequests.delete(url));
pendingRequests.set(url, promise);
return promise;
}
分层缓存策略:
- 内存缓存:用于短期高频数据
- SessionStorage:会话级缓存
- IndexedDB:长期存储大型数据
缓存更新策略:javascript
async function getWithCacheUpdate(url) {
const cachedData = cache.get(url);
const freshData = await fetch(url).then(res => res.json());// 后台静默更新缓存
if (JSON.stringify(cachedData) !== JSON.stringify(freshData)) {
cache.set(url, freshData);
}return cachedData || freshData;
}
五、实战中的注意事项
- 敏感数据缓存:切勿缓存认证令牌等敏感信息
- 缓存分区:按用户ID划分缓存空间,避免数据污染
缓存击穿防护:javascript
async function safeGet(key, loader) {
const value = cache.get(key);
if (value !== undefined) return value;// 加锁防止缓存击穿
const promise = loader();
cache.set(key, promise); // 先存入Promise对象try {
const result = await promise;
cache.set(key, result); // 替换为实际结果
return result;
} catch (err) {
cache.delete(key);
throw err;
}
}
在Vue/React项目中,可以将缓存逻辑封装成自定义Hook或高阶组件。例如在React中:
jsx
function useCachedRequest(url) {
const [data, setData] = useState(null);
const cache = useContext(CacheContext);
useEffect(() => {
const cached = cache.get(url);
if (cached) {
setData(cached);
return;
}
fetch(url)
.then(res => res.json())
.then(json => {
cache.set(url, json);
setData(json);
});
}, [url, cache]);
return data;
}
缓存策略需要根据具体业务场景调整。内容型网站可能采用长时间缓存,而实时交易系统则需要更短的缓存周期。关键在于找到性能体验与数据实时性的平衡点。