前端缓存 API 请求数据的解决方案详解
本文系统介绍了前端缓存技术,涵盖从基础到高级的实现方案。主要内容包括:浏览器原生缓存(WebStorage、IndexedDB、CacheAPI)、内存缓存实现(基础内存缓存和LRU策略)、请求层缓存方案(Axios和Fetch封装),以及高级缓存策略(分层缓存、读写策略、预加载)。文章还提供了缓存监控、调试工具和性能优化方法,并演示了在React和Vue中的集成方案。最后总结了不同规模项目的缓存
1. 前端缓存概述与价值
1.1 为什么需要前端缓存
在前端开发中,频繁的 API 请求会导致一系列问题:
-
性能瓶颈:网络请求是前端应用中最耗时的操作之一
-
用户体验下降:加载状态和等待时间影响用户满意度
-
服务器压力:重复请求增加后端负担
-
流量消耗:移动端用户可能面临额外流量费用
1.2 前端缓存的核心价值
javascript
// 缓存带来的性能提升示例
const withoutCache = async () => {
// 每次都需要网络请求
const response = await fetch('/api/data');
return response.json();
};
const withCache = async () => {
// 使用缓存,大幅减少请求次数
const cached = localStorage.getItem('api_data');
if (cached) {
return JSON.parse(cached);
}
const response = await fetch('/api/data');
const data = await response.json();
localStorage.setItem('api_data', JSON.stringify(data));
return data;
};
2. 浏览器原生缓存方案
2.1 Web Storage API
2.1.1 localStorage 持久化缓存
javascript
class LocalStorageCache {
constructor(namespace = 'app_cache') {
this.namespace = namespace;
this.defaultTTL = 60 * 60 * 1000; // 1小时默认过期时间
}
// 生成存储键名
generateKey(key) {
return `${this.namespace}:${key}`;
}
// 设置缓存
set(key, value, ttl = this.defaultTTL) {
try {
const cacheItem = {
value,
expires: Date.now() + ttl,
createdAt: Date.now()
};
localStorage.setItem(
this.generateKey(key),
JSON.stringify(cacheItem)
);
return true;
} catch (error) {
console.warn('LocalStorage 写入失败:', error);
this.evictOldEntries(); // 尝试清理后重试
return false;
}
}
// 获取缓存
get(key) {
try {
const cached = localStorage.getItem(this.generateKey(key));
if (!cached) return null;
const cacheItem = JSON.parse(cached);
// 检查是否过期
if (Date.now() > cacheItem.expires) {
this.delete(key);
return null;
}
return cacheItem.value;
} catch (error) {
console.warn('LocalStorage 读取失败:', error);
return null;
}
}
// 删除缓存
delete(key) {
localStorage.removeItem(this.generateKey(key));
}
// 清理过期条目
evictOldEntries() {
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(this.namespace)) {
try {
const cached = localStorage.getItem(key);
const cacheItem = JSON.parse(cached);
if (Date.now() > cacheItem.expires) {
keysToRemove.push(key);
}
} catch {
keysToRemove.push(key);
}
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
}
// 清空所有缓存
clear() {
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith(this.namespace)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
}
}
2.1.2 sessionStorage 会话缓存
javascript
class SessionCache {
constructor(namespace = 'session_cache') {
this.namespace = namespace;
}
generateKey(key) {
return `${this.namespace}:${key}`;
}
set(key, value) {
try {
sessionStorage.setItem(this.generateKey(key), JSON.stringify(value));
return true;
} catch (error) {
console.warn('sessionStorage 写入失败:', error);
return false;
}
}
get(key) {
try {
const cached = sessionStorage.getItem(this.generateKey(key));
return cached ? JSON.parse(cached) : null;
} catch (error) {
console.warn('sessionStorage 读取失败:', error);
return null;
}
}
delete(key) {
sessionStorage.removeItem(this.generateKey(key));
}
}
2.2 IndexedDB 大规模数据缓存
javascript
class IndexedDBCache {
constructor(dbName = 'APICache', version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
// 打开数据库连接
async open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建对象存储空间
if (!db.objectStoreNames.contains('cache')) {
const store = db.createObjectStore('cache', { keyPath: 'key' });
store.createIndex('expires', 'expires', { unique: false });
store.createIndex('createdAt', 'createdAt', { unique: false });
}
};
});
}
// 设置缓存
async set(key, value, ttl = 3600000) {
if (!this.db) await this.open();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['cache'], 'readwrite');
const store = transaction.objectStore('cache');
const cacheItem = {
key,
value,
expires: Date.now() + ttl,
createdAt: Date.now()
};
const request = store.put(cacheItem);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve();
});
}
// 获取缓存
async get(key) {
if (!this.db) await this.open();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['cache'], 'readonly');
const store = transaction.objectStore('cache');
const request = store.get(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const result = request.result;
if (!result) {
resolve(null);
return;
}
// 检查过期
if (Date.now() > result.expires) {
this.delete(key);
resolve(null);
return;
}
resolve(result.value);
};
});
}
// 删除缓存
async delete(key) {
if (!this.db) await this.open();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['cache'], 'readwrite');
const store = transaction.objectStore('cache');
const request = store.delete(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve();
});
}
// 清理过期缓存
async cleanup() {
if (!this.db) await this.open();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['cache'], 'readwrite');
const store = transaction.objectStore('cache');
const index = store.index('expires');
const range = IDBKeyRange.upperBound(Date.now());
const request = index.openCursor(range);
request.onerror = () => reject(request.error);
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
} else {
resolve();
}
};
});
}
}
2.3 Cache API (Service Worker)
javascript
// service-worker.js
const CACHE_NAME = 'api-cache-v1';
const API_CACHE_MAX_AGE = 3600000; // 1小时
// 安装 Service Worker
self.addEventListener('install', (event) => {
console.log('Service Worker 安装中...');
self.skipWaiting();
});
// 激活 Service Worker
self.addEventListener('activate', (event) => {
console.log('Service Worker 激活');
event.waitUntil(self.clients.claim());
});
// 拦截 fetch 请求
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// 只缓存 API 请求
if (!url.pathname.startsWith('/api/')) {
return;
}
// 只缓存 GET 请求
if (event.request.method !== 'GET') {
return;
}
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
// 如果有缓存且未过期,返回缓存
if (cachedResponse) {
const cachedTime = new Date(
cachedResponse.headers.get('sw-cache-time')
).getTime();
if (Date.now() - cachedTime < API_CACHE_MAX_AGE) {
return cachedResponse;
}
}
// 否则发起网络请求
return fetch(event.request).then((networkResponse) => {
// 克隆响应以便缓存
const responseToCache = networkResponse.clone();
// 添加缓存时间头
const cachedHeaders = new Headers(responseToCache.headers);
cachedHeaders.append('sw-cache-time', new Date().toISOString());
const cachedResponse = new Response(responseToCache.body, {
status: responseToCache.status,
statusText: responseToCache.statusText,
headers: cachedHeaders
});
// 缓存响应
cache.put(event.request, cachedResponse);
return networkResponse;
});
});
})
);
});
3. 内存缓存解决方案
3.1 基础内存缓存实现
javascript
class MemoryCache {
constructor(options = {}) {
this.cache = new Map();
this.defaultTTL = options.defaultTTL || 300000; // 5分钟
this.checkPeriod = options.checkPeriod || 60000; // 1分钟检查一次
this.maxSize = options.maxSize || 1000;
// 启动过期检查
this.startExpirationCheck();
}
set(key, value, ttl = this.defaultTTL) {
// 如果超过最大大小,删除最旧的条目
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
const expires = Date.now() + ttl;
this.cache.set(key, {
value,
expires,
createdAt: Date.now()
});
return true;
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
// 检查是否过期
if (Date.now() > item.expires) {
this.cache.delete(key);
return null;
}
return item.value;
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
has(key) {
return this.cache.has(key);
}
size() {
return this.cache.size;
}
keys() {
return Array.from(this.cache.keys());
}
// 获取所有未过期的键
validKeys() {
const now = Date.now();
return Array.from(this.cache.entries())
.filter(([key, item]) => now <= item.expires)
.map(([key]) => key);
}
// 启动过期检查定时器
startExpirationCheck() {
this.expirationTimer = setInterval(() => {
const now = Date.now();
for (const [key, item] of this.cache.entries()) {
if (now > item.expires) {
this.cache.delete(key);
}
}
}, this.checkPeriod);
}
// 停止过期检查
stopExpirationCheck() {
if (this.expirationTimer) {
clearInterval(this.expirationTimer);
}
}
}
3.2 高级内存缓存 with LRU 策略
javascript
class LRUMemoryCache extends MemoryCache {
constructor(options = {}) {
super(options);
this.accessOrder = []; // 维护访问顺序
}
set(key, value, ttl = this.defaultTTL) {
super.set(key, value, ttl);
// 更新访问顺序
this.updateAccessOrder(key);
return true;
}
get(key) {
const value = super.get(key);
if (value) {
this.updateAccessOrder(key);
}
return value;
}
delete(key) {
const deleted = super.delete(key);
if (deleted) {
const index = this.accessOrder.indexOf(key);
if (index > -1) {
this.accessOrder.splice(index, 1);
}
}
return deleted;
}
updateAccessOrder(key) {
// 移除现有的键(如果存在)
const existingIndex = this.accessOrder.indexOf(key);
if (existingIndex > -1) {
this.accessOrder.splice(existingIndex, 1);
}
// 添加到末尾(最新访问)
this.accessOrder.push(key);
}
// 重写 set 方法以处理大小限制
set(key, value, ttl = this.defaultTTL) {
// 如果键已存在,先删除以更新顺序
if (this.cache.has(key)) {
this.delete(key);
}
// 如果超过大小限制,删除最久未使用的
if (this.cache.size >= this.maxSize) {
const lruKey = this.accessOrder[0];
this.delete(lruKey);
}
super.set(key, value, ttl);
this.accessOrder.push(key);
return true;
}
}
4. 请求层缓存方案
4.1 Axios 拦截器缓存
javascript
class AxiosCacheInterceptor {
constructor(axiosInstance, options = {}) {
this.axios = axiosInstance;
this.cache = options.cache || new MemoryCache();
this.defaultTTL = options.defaultTTL || 300000;
this.setupInterceptors();
}
setupInterceptors() {
// 请求拦截器 - 检查缓存
this.axios.interceptors.request.use(
(config) => {
// 只有 GET 请求才缓存
if (config.method?.toLowerCase() !== 'get') {
return config;
}
// 检查是否禁用缓存
if (config.headers['X-No-Cache'] === 'true') {
return config;
}
const cacheKey = this.generateCacheKey(config);
const cachedData = this.cache.get(cacheKey);
if (cachedData) {
// 返回缓存的响应
return {
...config,
adapter: () => Promise.resolve({
data: cachedData,
status: 200,
statusText: 'OK',
headers: {},
config,
request: {}
})
};
}
// 添加缓存键到配置中,供响应拦截器使用
config.cacheKey = cacheKey;
config.cacheTTL = config.cacheTTL || this.defaultTTL;
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器 - 缓存响应
this.axios.interceptors.response.use(
(response) => {
const { config, data } = response;
if (config.cacheKey && data) {
this.cache.set(
config.cacheKey,
data,
config.cacheTTL
);
}
return response;
},
(error) => Promise.reject(error)
);
}
generateCacheKey(config) {
const { url, method, params, data } = config;
// 基于请求参数生成唯一键
const keyParts = [
method,
url,
JSON.stringify(params),
JSON.stringify(data)
];
return btoa(keyParts.join('|')).substring(0, 64);
}
// 手动清除特定 URL 的缓存
clearCache(urlPattern) {
const keys = this.cache.keys();
keys.forEach(key => {
if (key.includes(urlPattern)) {
this.cache.delete(key);
}
});
}
// 清除所有缓存
clearAllCache() {
this.cache.clear();
}
}
// 使用示例
const axiosInstance = axios.create();
const cacheInterceptor = new AxiosCacheInterceptor(axiosInstance, {
defaultTTL: 60000, // 1分钟
cache: new LRUMemoryCache({ maxSize: 100 })
});
4.2 Fetch API 缓存封装
javascript
class FetchWithCache {
constructor(options = {}) {
this.cache = options.cache || new MemoryCache();
this.defaultTTL = options.defaultTTL || 300000;
this.defaultOptions = options.defaultOptions || {};
}
async fetch(url, options = {}) {
const mergedOptions = { ...this.defaultOptions, ...options };
const method = mergedOptions.method?.toUpperCase() || 'GET';
// 只有 GET 请求使用缓存
if (method !== 'GET' || mergedOptions.noCache) {
return this.fetchWithoutCache(url, mergedOptions);
}
const cacheKey = this.generateCacheKey(url, mergedOptions);
const cachedResponse = this.cache.get(cacheKey);
if (cachedResponse) {
return this.createResponseClone(cachedResponse);
}
// 发起实际请求
const response = await fetch(url, mergedOptions);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
// 缓存响应
this.cache.set(cacheKey, responseData, mergedOptions.cacheTTL || this.defaultTTL);
return this.createResponseClone(responseData);
}
async fetchWithoutCache(url, options = {}) {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
generateCacheKey(url, options) {
const { params, body, headers } = options;
const keyParts = [
url,
JSON.stringify(params),
JSON.stringify(body),
JSON.stringify(headers)
];
return btoa(keyParts.join('|')).substring(0, 64);
}
createResponseClone(data) {
return {
data,
status: 200,
ok: true,
cloned: true,
json: () => Promise.resolve(data)
};
}
// 预加载数据到缓存
async preload(url, options = {}) {
try {
const data = await this.fetchWithoutCache(url, options);
const cacheKey = this.generateCacheKey(url, options);
this.cache.set(cacheKey, data, options.cacheTTL || this.defaultTTL);
return true;
} catch (error) {
console.warn(`预加载失败: ${url}`, error);
return false;
}
}
// 清除特定模式的缓存
clearCache(pattern) {
const keys = this.cache.keys();
const regex = new RegExp(pattern);
keys.forEach(key => {
if (regex.test(key)) {
this.cache.delete(key);
}
});
}
}
// 使用示例
const cachedFetch = new FetchWithCache({
defaultTTL: 60000,
cache: new LRUMemoryCache({ maxSize: 50 })
});
// 使用缓存 fetch
cachedFetch.fetch('/api/users')
.then(data => console.log('Data:', data))
.catch(error => console.error('Error:', error));
5. 高级缓存策略与模式
5.1 缓存分层策略
javascript
class LayeredCache {
constructor(layers = []) {
this.layers = layers;
}
async get(key) {
// 从最上层开始查找
for (let i = 0; i < this.layers.length; i++) {
const layer = this.layers[i];
const value = await layer.get(key);
if (value !== null && value !== undefined) {
// 找到值,更新上层缓存
for (let j = 0; j < i; j++) {
this.layers[j].set(key, value);
}
return value;
}
}
return null;
}
async set(key, value, ttl) {
// 设置所有层级的缓存
const promises = this.layers.map(layer =>
layer.set(key, value, ttl)
);
await Promise.all(promises);
}
async delete(key) {
// 删除所有层级的缓存
const promises = this.layers.map(layer =>
layer.delete(key)
);
await Promise.all(promises);
}
addLayer(layer) {
this.layers.push(layer);
}
}
// 使用分层缓存
const layeredCache = new LayeredCache([
new MemoryCache({ maxSize: 100 }), // L1: 内存缓存
new LocalStorageCache('app_cache'), // L2: 本地存储
new IndexedDBCache() // L3: IndexedDB
]);
5.2 缓存更新策略
5.2.1 写穿策略 (Write-Through)
javascript
class WriteThroughCache {
constructor(cache, fetchFunction) {
this.cache = cache;
this.fetchFunction = fetchFunction;
}
async get(key) {
let value = await this.cache.get(key);
if (value === null) {
// 缓存未命中,从数据源获取
value = await this.fetchFunction(key);
if (value !== null) {
// 写入缓存
await this.cache.set(key, value);
}
}
return value;
}
async set(key, value) {
// 同时更新缓存和数据源
await Promise.all([
this.cache.set(key, value),
this.updateDataSource(key, value)
]);
}
async updateDataSource(key, value) {
// 这里实现实际的数据源更新逻辑
// 例如调用 API 更新后端数据
console.log(`更新数据源: ${key}`, value);
}
}
5.2.2 写回策略 (Write-Back)
javascript
class WriteBackCache {
constructor(cache, updateFunction, options = {}) {
this.cache = cache;
this.updateFunction = updateFunction;
this.dirtyKeys = new Set();
this.flushInterval = options.flushInterval || 30000; // 30秒
this.batchSize = options.batchSize || 10;
this.startFlushTimer();
}
async get(key) {
return this.cache.get(key);
}
async set(key, value) {
await this.cache.set(key, value);
this.dirtyKeys.add(key);
}
async delete(key) {
await this.cache.delete(key);
this.dirtyKeys.delete(key);
}
startFlushTimer() {
setInterval(() => {
this.flushDirtyKeys();
}, this.flushInterval);
}
async flushDirtyKeys() {
if (this.dirtyKeys.size === 0) return;
const keys = Array.from(this.dirtyKeys);
const batches = [];
// 分批处理
for (let i = 0; i < keys.length; i += this.batchSize) {
batches.push(keys.slice(i, i + this.batchSize));
}
for (const batch of batches) {
try {
await this.flushBatch(batch);
} catch (error) {
console.error('批量更新失败:', error);
}
}
}
async flushBatch(keys) {
const updates = [];
for (const key of keys) {
const value = await this.cache.get(key);
if (value !== null) {
updates.push({ key, value });
}
}
if (updates.length > 0) {
await this.updateFunction(updates);
// 移除已刷新的脏键
for (const { key } of updates) {
this.dirtyKeys.delete(key);
}
}
}
// 手动刷新所有脏键
async flushAll() {
await this.flushDirtyKeys();
}
}
5.3 缓存预热与预加载
javascript
class CachePreloader {
constructor(cache, options = {}) {
this.cache = cache;
this.preloadQueue = new Map();
this.maxConcurrent = options.maxConcurrent || 3;
this.currentPreloads = 0;
}
// 添加预加载任务
addPreload(key, fetchFunction, priority = 0) {
this.preloadQueue.set(key, {
fetchFunction,
priority,
addedAt: Date.now()
});
this.processQueue();
}
// 处理预加载队列
async processQueue() {
if (this.currentPreloads >= this.maxConcurrent) {
return;
}
if (this.preloadQueue.size === 0) {
return;
}
// 按优先级排序
const sortedQueue = Array.from(this.preloadQueue.entries())
.sort((a, b) => {
const priorityDiff = b[1].priority - a[1].priority;
if (priorityDiff !== 0) return priorityDiff;
return a[1].addedAt - b[1].addedAt;
});
const availableSlots = this.maxConcurrent - this.currentPreloads;
const toPreload = sortedQueue.slice(0, availableSlots);
for (const [key, task] of toPreload) {
this.preloadQueue.delete(key);
this.executePreload(key, task);
}
}
async executePreload(key, task) {
this.currentPreloads++;
try {
const data = await task.fetchFunction();
await this.cache.set(key, data);
console.log(`预加载成功: ${key}`);
} catch (error) {
console.warn(`预加载失败: ${key}`, error);
} finally {
this.currentPreloads--;
this.processQueue(); // 继续处理队列
}
}
// 批量预加载
async bulkPreload(preloadList) {
for (const item of preloadList) {
this.addPreload(item.key, item.fetchFunction, item.priority);
}
}
}
// 使用示例
const preloader = new CachePreloader(layeredCache, {
maxConcurrent: 2
});
// 添加预加载任务
preloader.addPreload(
'user_profile_123',
() => fetch('/api/users/123').then(r => r.json()),
10 // 高优先级
);
6. 缓存监控与调试
6.1 缓存性能监控
javascript
class CacheMonitor {
constructor(cache, options = {}) {
this.cache = cache;
this.metrics = {
hits: 0,
misses: 0,
sets: 0,
deletes: 0,
errors: 0,
startTime: Date.now()
};
this.setupMonitoring();
}
setupMonitoring() {
const originalGet = this.cache.get.bind(this.cache);
const originalSet = this.cache.set.bind(this.cache);
const originalDelete = this.cache.delete.bind(this.cache);
// 包装 get 方法
this.cache.get = async (key) => {
try {
const startTime = performance.now();
const result = await originalGet(key);
const duration = performance.now() - startTime;
if (result !== null && result !== undefined) {
this.metrics.hits++;
} else {
this.metrics.misses++;
}
this.recordTiming('get', duration);
return result;
} catch (error) {
this.metrics.errors++;
throw error;
}
};
// 包装 set 方法
this.cache.set = async (key, value, ttl) => {
try {
const startTime = performance.now();
const result = await originalSet(key, value, ttl);
const duration = performance.now() - startTime;
this.metrics.sets++;
this.recordTiming('set', duration);
return result;
} catch (error) {
this.metrics.errors++;
throw error;
}
};
// 包装 delete 方法
this.cache.delete = async (key) => {
try {
const startTime = performance.now();
const result = await originalDelete(key);
const duration = performance.now() - startTime;
this.metrics.deletes++;
this.recordTiming('delete', duration);
return result;
} catch (error) {
this.metrics.errors++;
throw error;
}
};
}
recordTiming(operation, duration) {
if (!this.metrics.timings) {
this.metrics.timings = {};
}
if (!this.metrics.timings[operation]) {
this.metrics.timings[operation] = {
count: 0,
total: 0,
min: Infinity,
max: -Infinity
};
}
const timing = this.metrics.timings[operation];
timing.count++;
timing.total += duration;
timing.min = Math.min(timing.min, duration);
timing.max = Math.max(timing.max, duration);
}
getMetrics() {
const uptime = Date.now() - this.metrics.startTime;
const hitRate = this.metrics.hits / (this.metrics.hits + this.metrics.misses) || 0;
const metrics = {
...this.metrics,
uptime,
hitRate: Math.round(hitRate * 10000) / 100 // 百分比,保留两位小数
};
// 计算平均耗时
if (this.metrics.timings) {
metrics.avgTimings = {};
for (const [operation, timing] of Object.entries(this.metrics.timings)) {
metrics.avgTimings[operation] = timing.total / timing.count;
}
}
return metrics;
}
resetMetrics() {
this.metrics = {
hits: 0,
misses: 0,
sets: 0,
deletes: 0,
errors: 0,
startTime: Date.now()
};
}
}
6.2 缓存调试工具
javascript
class CacheDebugger {
constructor(cache) {
this.cache = cache;
this.logs = [];
this.maxLogs = 1000;
}
log(level, message, data = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
data
};
this.logs.push(logEntry);
// 保持日志数量在限制内
if (this.logs.length > this.maxLogs) {
this.logs.shift();
}
// 控制台输出
if (level === 'error') {
console.error(`[Cache] ${message}`, data);
} else if (level === 'warn') {
console.warn(`[Cache] ${message}`, data);
} else {
console.log(`[Cache] ${message}`, data);
}
}
// 获取缓存状态快照
async getSnapshot() {
const snapshot = {
timestamp: new Date().toISOString(),
size: this.cache.size ? await this.cache.size() : 'N/A',
keys: this.cache.keys ? await this.cache.keys() : []
};
return snapshot;
}
// 检查缓存健康状态
async healthCheck() {
const issues = [];
try {
// 测试读写
const testKey = '__health_check__';
const testValue = { test: true, timestamp: Date.now() };
await this.cache.set(testKey, testValue, 60000);
const retrieved = await this.cache.get(testKey);
if (!retrieved || retrieved.test !== true) {
issues.push('读写测试失败');
}
await this.cache.delete(testKey);
} catch (error) {
issues.push(`健康检查失败: ${error.message}`);
}
return {
healthy: issues.length === 0,
issues,
timestamp: new Date().toISOString()
};
}
// 生成缓存报告
async generateReport() {
const snapshot = await this.getSnapshot();
const health = await this.healthCheck();
return {
snapshot,
health,
logs: this.logs.slice(-100), // 最近100条日志
summary: {
totalLogs: this.logs.length,
errorLogs: this.logs.filter(log => log.level === 'error').length,
warningLogs: this.logs.filter(log => log.level === 'warn').length
}
};
}
}
7. 实战应用示例
7.1 React Hook 缓存集成
javascript
import { useState, useEffect, useRef } from 'react';
// 创建全局缓存实例
const globalCache = new LayeredCache([
new MemoryCache({ maxSize: 100 }),
new LocalStorageCache('react_app')
]);
function useCachedFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const cacheKeyRef = useRef(null);
const {
cacheTTL = 300000,
enabled = true,
revalidateOnFocus = false,
revalidateOnReconnect = false
} = options;
useEffect(() => {
if (!enabled) return;
cacheKeyRef.current = generateCacheKey(url, options);
fetchData();
// 可选:焦点重新验证
if (revalidateOnFocus) {
const handleFocus = () => fetchData(true);
window.addEventListener('focus', handleFocus);
return () => window.removeEventListener('focus', handleFocus);
}
// 可选:网络重新连接验证
if (revalidateOnReconnect) {
const handleOnline = () => fetchData(true);
window.addEventListener('online', handleOnline);
return () => window.removeEventListener('online', handleOnline);
}
}, [url, JSON.stringify(options), enabled]);
const fetchData = async (forceRevalidate = false) => {
if (!forceRevalidate) {
const cached = await globalCache.get(cacheKeyRef.current);
if (cached) {
setData(cached);
setLoading(false);
return;
}
}
setLoading(true);
setError(null);
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
await globalCache.set(cacheKeyRef.current, result, cacheTTL);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const mutate = async (newData, shouldRevalidate = true) => {
if (newData) {
setData(newData);
await globalCache.set(cacheKeyRef.current, newData, cacheTTL);
}
if (shouldRevalidate) {
await fetchData(true);
}
};
const clearCache = () => {
globalCache.delete(cacheKeyRef.current);
};
return {
data,
loading,
error,
mutate,
clearCache,
refetch: () => fetchData(true)
};
}
function generateCacheKey(url, options) {
const { method = 'GET', body, headers } = options;
const keyParts = [
method,
url,
JSON.stringify(body),
JSON.stringify(headers)
];
return btoa(keyParts.join('|')).substring(0, 64);
}
// 使用示例
function UserProfile({ userId }) {
const { data: user, loading, error } = useCachedFetch(
`/api/users/${userId}`,
{
cacheTTL: 60000, // 1分钟
revalidateOnFocus: true
}
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
7.2 Vue 组合式 API 缓存集成
javascript
import { ref, computed, watch, onUnmounted } from 'vue';
class VueCacheManager {
constructor() {
this.cache = new LayeredCache([
new MemoryCache({ maxSize: 200 }),
new LocalStorageCache('vue_app')
]);
this.subscriptions = new Map();
}
useCachedFetch(url, options = {}) {
const data = ref(null);
const loading = ref(true);
const error = ref(null);
const {
cacheTTL = 300000,
enabled = true,
immediate = true
} = options;
const cacheKey = computed(() =>
this.generateCacheKey(url.value || url, options)
);
const execute = async (force = false) => {
if (!enabled) return;
// 检查缓存
if (!force) {
const cached = await this.cache.get(cacheKey.value);
if (cached) {
data.value = cached;
loading.value = false;
return;
}
}
loading.value = true;
error.value = null;
try {
const actualUrl = url.value || url;
const response = await fetch(actualUrl, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
data.value = result;
await this.cache.set(cacheKey.value, result, cacheTTL);
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};
// 自动执行
if (immediate) {
execute();
}
// 响应式 URL 监听
if (typeof url === 'object') {
watch(url, () => execute());
}
const mutate = async (newData, shouldRevalidate = true) => {
if (newData) {
data.value = newData;
await this.cache.set(cacheKey.value, newData, cacheTTL);
}
if (shouldRevalidate) {
await execute(true);
}
};
const clear = () => {
this.cache.delete(cacheKey.value);
};
return {
data,
loading,
error,
execute: () => execute(true),
mutate,
clear
};
}
generateCacheKey(url, options) {
const { method = 'GET', body, headers } = options;
const keyParts = [
method,
url,
JSON.stringify(body),
JSON.stringify(headers)
];
return btoa(keyParts.join('|')).substring(0, 64);
}
}
// 创建全局缓存实例
const cacheManager = new VueCacheManager();
// 使用示例
export function useUserProfile(userId) {
return cacheManager.useCachedFetch(
computed(() => `/api/users/${userId.value}`),
{
cacheTTL: 60000,
enabled: computed(() => !!userId.value)
}
);
}
8. 最佳实践与性能优化
8.1 缓存键设计最佳实践
javascript
class CacheKeyStrategy {
// 1. 一致性键生成
static generateConsistentKey(components) {
const normalized = components.map(comp => {
if (typeof comp === 'object') {
return JSON.stringify(comp, Object.keys(comp).sort());
}
return String(comp);
});
return btoa(normalized.join('::')).substring(0, 64);
}
// 2. 命名空间管理
static withNamespace(namespace, key) {
return `${namespace}::${key}`;
}
// 3. 版本控制
static withVersion(version, key) {
return `v${version}::${key}`;
}
// 4. 用户隔离
static withUserContext(userId, key) {
return `user:${userId}::${key}`;
}
// 5. 完整键生成方案
static generateFullKey({
namespace,
version,
userId,
operation,
params
}) {
const components = [operation];
if (params) {
components.push(params);
}
let key = this.generateConsistentKey(components);
if (namespace) {
key = this.withNamespace(namespace, key);
}
if (version) {
key = this.withVersion(version, key);
}
if (userId) {
key = this.withUserContext(userId, key);
}
return key;
}
}
// 使用示例
const cacheKey = CacheKeyStrategy.generateFullKey({
namespace: 'api',
version: 1,
userId: '12345',
operation: 'getUserProfile',
params: { include: 'settings,preferences' }
});
8.2 内存优化策略
javascript
class MemoryOptimizedCache extends MemoryCache {
constructor(options = {}) {
super(options);
this.maxMemoryUsage = options.maxMemoryUsage || 50 * 1024 * 1024; // 50MB
this.currentMemoryUsage = 0;
this.enableCompression = options.enableCompression || false;
}
estimateSize(value) {
try {
const jsonString = JSON.stringify(value);
return new Blob([jsonString]).size;
} catch {
return 1024; // 默认1KB
}
}
compress(value) {
if (!this.enableCompression) return value;
// 简单的压缩策略:移除 null/undefined,缩短键名等
if (typeof value === 'object' && value !== null) {
const compressed = {};
for (const [key, val] of Object.entries(value)) {
if (val !== null && val !== undefined) {
// 使用短键名(生产环境中可以使用映射表)
const shortKey = key.substring(0, 3);
compressed[shortKey] = val;
}
}
return compressed;
}
return value;
}
decompress(value) {
// 对应的解压逻辑
return value;
}
set(key, value, ttl = this.defaultTTL) {
const compressedValue = this.compress(value);
const itemSize = this.estimateSize(compressedValue);
// 检查内存限制
if (this.currentMemoryUsage + itemSize > this.maxMemoryUsage) {
this.evictLRUItems(itemSize);
}
const success = super.set(key, compressedValue, ttl);
if (success) {
this.currentMemoryUsage += itemSize;
}
return success;
}
get(key) {
const compressedValue = super.get(key);
return compressedValue ? this.decompress(compressedValue) : null;
}
delete(key) {
const item = this.cache.get(key);
if (item) {
const itemSize = this.estimateSize(item.value);
this.currentMemoryUsage -= itemSize;
}
return super.delete(key);
}
evictLRUItems(requiredSpace) {
let freedSpace = 0;
const entries = Array.from(this.cache.entries())
.sort((a, b) => a[1].createdAt - b[1].createdAt); // 按创建时间排序
for (const [key, item] of entries) {
if (freedSpace >= requiredSpace) break;
const itemSize = this.estimateSize(item.value);
this.delete(key);
freedSpace += itemSize;
}
}
}
8.3 错误处理与降级策略
javascript
class ResilientCache {
constructor(primaryCache, fallbackCaches = []) {
this.primary = primaryCache;
this.fallbacks = fallbackCaches;
this.circuitBreaker = new CircuitBreaker();
}
async get(key) {
if (this.circuitBreaker.isOpen()) {
return this.tryFallbacks(key);
}
try {
const value = await this.primary.get(key);
this.circuitBreaker.recordSuccess();
return value;
} catch (error) {
this.circuitBreaker.recordFailure();
console.warn('Primary cache failed, trying fallbacks:', error);
return this.tryFallbacks(key);
}
}
async set(key, value, ttl) {
if (this.circuitBreaker.isOpen()) {
return this.setFallbacks(key, value, ttl);
}
try {
await this.primary.set(key, value, ttl);
this.circuitBreaker.recordSuccess();
// 异步更新后备缓存
this.setFallbacks(key, value, ttl).catch(console.error);
} catch (error) {
this.circuitBreaker.recordFailure();
console.warn('Primary cache set failed:', error);
await this.setFallbacks(key, value, ttl);
}
}
async tryFallbacks(key) {
for (const fallback of this.fallbacks) {
try {
const value = await fallback.get(key);
if (value !== null) return value;
} catch (error) {
console.warn(`Fallback cache failed:`, error);
}
}
return null;
}
async setFallbacks(key, value, ttl) {
const promises = this.fallbacks.map(fallback =>
fallback.set(key, value, ttl).catch(console.warn)
);
await Promise.allSettled(promises);
}
}
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 60000;
this.failures = 0;
this.lastFailureTime = null;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
}
recordSuccess() {
this.state = 'CLOSED';
this.failures = 0;
}
recordFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
setTimeout(() => {
this.state = 'HALF_OPEN';
}, this.resetTimeout);
}
}
isOpen() {
if (this.state === 'OPEN') return true;
if (this.state === 'HALF_OPEN') {
this.state = 'OPEN'; // 下次请求再试
return false;
}
return false;
}
}
9. 测试策略
9.1 单元测试
javascript
describe('MemoryCache', () => {
let cache;
beforeEach(() => {
cache = new MemoryCache({ defaultTTL: 1000 });
});
afterEach(() => {
cache.stopExpirationCheck();
});
test('should set and get value', async () => {
await cache.set('key1', 'value1');
const result = await cache.get('key1');
expect(result).toBe('value1');
});
test('should return null for expired items', async () => {
await cache.set('key1', 'value1', 10); // 10ms TTL
await new Promise(resolve => setTimeout(resolve, 20));
const result = await cache.get('key1');
expect(result).toBeNull();
});
test('should respect max size limit', async () => {
const smallCache = new MemoryCache({ maxSize: 2 });
await smallCache.set('key1', 'value1');
await smallCache.set('key2', 'value2');
await smallCache.set('key3', 'value3');
expect(smallCache.size()).toBe(2);
expect(smallCache.get('key1')).toBeNull();
});
});
describe('LayeredCache', () => {
let layer1, layer2, layeredCache;
beforeEach(() => {
layer1 = new MemoryCache();
layer2 = new MemoryCache();
layeredCache = new LayeredCache([layer1, layer2]);
});
test('should check all layers', async () => {
await layer2.set('key1', 'value1');
const result = await layeredCache.get('key1');
expect(result).toBe('value1');
// 应该已经填充到 layer1
const layer1Result = await layer1.get('key1');
expect(layer1Result).toBe('value1');
});
});
9.2 性能测试
javascript
class CacheBenchmark {
constructor() {
this.results = new Map();
}
async runBenchmark(cache, operations = 1000) {
const results = {
set: { times: [], average: 0 },
get: { times: [], average: 0 },
hitRate: 0
};
// 测试设置性能
for (let i = 0; i < operations; i++) {
const key = `key_${i}`;
const value = { data: `value_${i}`, index: i };
const start = performance.now();
await cache.set(key, value);
const end = performance.now();
results.set.times.push(end - start);
}
// 测试读取性能(缓存命中)
for (let i = 0; i < operations; i++) {
const key = `key_${i}`;
const start = performance.now();
await cache.get(key);
const end = performance.now();
results.get.times.push(end - start);
}
// 测试读取性能(缓存未命中)
for (let i = operations; i < operations * 2; i++) {
const key = `key_${i}`;
const start = performance.now();
await cache.get(key);
const end = performance.now();
results.get.times.push(end - start);
}
// 计算平均值
results.set.average = results.set.times.reduce((a, b) => a + b, 0) / results.set.times.length;
results.get.average = results.get.times.reduce((a, b) => a + b, 0) / results.get.times.length;
// 计算命中率(简化版)
results.hitRate = operations / (operations * 2);
this.results.set(cache.constructor.name, results);
return results;
}
generateReport() {
const report = [];
for (const [name, result] of this.results) {
report.push({
Cache: name,
'Set Avg (ms)': result.set.average.toFixed(3),
'Get Avg (ms)': result.get.average.toFixed(3),
'Hit Rate': (result.hitRate * 100).toFixed(1) + '%'
});
}
console.table(report);
}
}
// 运行性能对比
async function runPerformanceComparison() {
const benchmark = new CacheBenchmark();
const caches = [
new MemoryCache(),
new LocalStorageCache('benchmark'),
new LayeredCache([
new MemoryCache(),
new LocalStorageCache('benchmark_layered')
])
];
for (const cache of caches) {
await benchmark.runBenchmark(cache, 100);
}
benchmark.generateReport();
}
10. 总结
前端缓存 API 请求数据是一个复杂但极其有价值的技术领域。通过本文的详细探讨,我们了解了:
-
多种缓存存储方案:从简单的内存缓存到复杂的 IndexedDB
-
智能缓存策略:分层缓存、写穿/写回策略、缓存预热等
-
框架集成:React Hook 和 Vue 组合式 API 的缓存集成
-
监控调试:完整的缓存监控和调试工具链
-
性能优化:内存优化、错误处理、降级策略
-
测试验证:全面的测试策略确保缓存可靠性
在实际项目中,建议根据具体需求选择合适的缓存方案:
-
简单应用:MemoryCache + localStorage
-
中型应用:LayeredCache + 智能策略
-
大型应用:完整的缓存生态系统 + 监控
为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐



所有评论(0)