前端缓存策略 🗄️
引言
缓存是提升Web应用性能的关键策略之一。合理的缓存策略可以显著减少网络请求,加快页面加载速度,提升用户体验。本文将深入探讨前端缓存的各种策略和最佳实践,帮助开发者构建高效的缓存系统。
缓存策略概述
前端缓存可以分为以下几个层面:
- HTTP缓存:利用浏览器的HTTP缓存机制
- 浏览器存储:LocalStorage、SessionStorage、IndexedDB等
- 应用缓存:Service Worker、应用级缓存
- CDN缓存:利用内容分发网络加速资源访问
- 数据缓存:前端数据状态管理和缓存
HTTP缓存策略
强缓存与协商缓存
typescript
// 服务器端设置HTTP缓存头
app.get('/api/data', (req, res) => {
// 强缓存:设置过期时间
res.setHeader('Cache-Control', 'max-age=3600'); // 1小时
res.setHeader('Expires', new Date(Date.now() + 3600000).toUTCString());
// 协商缓存:设置ETag和Last-Modified
const etag = generateETag(data);
const lastModified = new Date().toUTCString();
res.setHeader('ETag', etag);
res.setHeader('Last-Modified', lastModified);
// 检查协商缓存
const ifNoneMatch = req.headers['if-none-match'];
const ifModifiedSince = req.headers['if-modified-since'];
if (ifNoneMatch === etag || new Date(ifModifiedSince) >= new Date(lastModified)) {
res.status(304).end(); // Not Modified
return;
}
res.json(data);
});
// 前端请求处理
async function fetchDataWithCache() {
try {
const response = await fetch('/api/data', {
headers: {
'Cache-Control': 'max-age=3600',
'If-None-Match': localStorage.getItem('etag'),
'If-Modified-Since': localStorage.getItem('lastModified')
}
});
if (response.status === 304) {
return JSON.parse(localStorage.getItem('cachedData') || '{}');
}
const data = await response.json();
// 保存缓存相关信息
localStorage.setItem('etag', response.headers.get('ETag') || '');
localStorage.setItem('lastModified', response.headers.get('Last-Modified') || '');
localStorage.setItem('cachedData', JSON.stringify(data));
return data;
} catch (error) {
console.error('数据获取失败:', error);
return JSON.parse(localStorage.getItem('cachedData') || '{}');
}
}
缓存控制策略
typescript
// 缓存控制工具类
class CacheController {
private static instance: CacheController;
private cacheConfig: Map<string, CacheStrategy>;
private constructor() {
this.cacheConfig = new Map();
}
static getInstance(): CacheController {
if (!CacheController.instance) {
CacheController.instance = new CacheController();
}
return CacheController.instance;
}
// 设置资源缓存策略
setCacheStrategy(resource: string, strategy: CacheStrategy): void {
this.cacheConfig.set(resource, strategy);
}
// 获取资源缓存策略
getCacheStrategy(resource: string): CacheStrategy | undefined {
return this.cacheConfig.get(resource);
}
}
interface CacheStrategy {
maxAge: number;
revalidate: boolean;
staleWhileRevalidate?: number;
}
// 使用示例
const cacheController = CacheController.getInstance();
// 设置不同资源的缓存策略
cacheController.setCacheStrategy('/api/user', {
maxAge: 3600,
revalidate: true,
staleWhileRevalidate: 300
});
cacheController.setCacheStrategy('/api/static-data', {
maxAge: 86400, // 24小时
revalidate: false
});
浏览器存储策略
LocalStorage与SessionStorage
typescript
// 浏览器存储管理类
class StorageManager {
private storage: Storage;
private prefix: string;
constructor(useSession: boolean = false, prefix: string = 'app_') {
this.storage = useSession ? sessionStorage : localStorage;
this.prefix = prefix;
}
// 存储数据
setItem(key: string, value: any, expires?: number): void {
const item = {
value,
timestamp: Date.now(),
expires: expires ? Date.now() + expires * 1000 : null
};
this.storage.setItem(this.prefix + key, JSON.stringify(item));
}
// 获取数据
getItem<T>(key: string): T | null {
const item = this.storage.getItem(this.prefix + key);
if (!item) return null;
const { value, timestamp, expires } = JSON.parse(item);
// 检查是否过期
if (expires && Date.now() > expires) {
this.removeItem(key);
return null;
}
return value as T;
}
// 删除数据
removeItem(key: string): void {
this.storage.removeItem(this.prefix + key);
}
// 清理过期数据
clearExpired(): void {
const keys = Object.keys(this.storage);
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
const item = this.storage.getItem(key);
if (item) {
const { expires } = JSON.parse(item);
if (expires && Date.now() > expires) {
this.storage.removeItem(key);
}
}
}
});
}
}
// 使用示例
const storage = new StorageManager();
// 存储用户数据,1小时后过期
storage.setItem('user', { id: 1, name: 'John' }, 3600);
// 获取用户数据
const user = storage.getItem<{ id: number, name: string }>('user');
// 定期清理过期数据
setInterval(() => storage.clearExpired(), 300000); // 每5分钟清理一次
IndexedDB缓存
typescript
// IndexedDB管理类
class IndexedDBManager {
private dbName: string;
private version: number;
constructor(dbName: string, version: number = 1) {
this.dbName = dbName;
this.version = version;
}
// 打开数据库
async openDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
// 创建存储对象
if (!db.objectStoreNames.contains('cache')) {
db.createObjectStore('cache', { keyPath: 'id' });
}
};
});
}
// 存储数据
async setCache(key: string, data: any): Promise<void> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['cache'], 'readwrite');
const store = transaction.objectStore('cache');
const request = store.put({
id: key,
data,
timestamp: Date.now()
});
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
// 获取数据
async getCache(key: string): Promise<any> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['cache'], 'readonly');
const store = transaction.objectStore('cache');
const request = store.get(key);
request.onsuccess = () => resolve(request.result?.data);
request.onerror = () => reject(request.error);
});
}
}
// 使用示例
const dbManager = new IndexedDBManager('myApp');
// 存储大量数据
await dbManager.setCache('userList', largeUserArray);
// 获取数据
const users = await dbManager.getCache('userList');
Service Worker缓存
基础缓存策略
typescript
// Service Worker安装
self.addEventListener('install', (event: ExtendableEvent) => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
]);
})
);
});
// 缓存优先策略
self.addEventListener('fetch', (event: FetchEvent) => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).then(response => {
// 克隆响应,因为响应流只能被读取一次
const responseClone = response.clone();
caches.open('v1').then(cache => {
cache.put(event.request, responseClone);
});
return response;
});
})
);
});
高级缓存策略
typescript
// 缓存策略管理器
class CacheStrategyManager {
// 网络优先策略
static async networkFirst(request: Request): Promise<Response> {
try {
// 尝试网络请求
const networkResponse = await fetch(request);
// 更新缓存
const cache = await caches.open('v1');
cache.put(request, networkResponse.clone());
return networkResponse;
} catch (error) {
// 网络请求失败,尝试使用缓存
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
throw new Error('No cached response available');
}
}
// 缓存优先策略
static async cacheFirst(request: Request): Promise<Response> {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
// 后台更新缓存
this.updateCache(request);
return cachedResponse;
}
return this.networkFirst(request);
}
// 后台更新缓存
private static async updateCache(request: Request): Promise<void> {
try {
const networkResponse = await fetch(request);
const cache = await caches.open('v1');
await cache.put(request, networkResponse);
} catch (error) {
console.warn('Background cache update failed:', error);
}
}
}
// 使用不同的缓存策略
self.addEventListener('fetch', (event: FetchEvent) => {
const url = new URL(event.request.url);
// API请求使用网络优先策略
if (url.pathname.startsWith('/api/')) {
event.respondWith(CacheStrategyManager.networkFirst(event.request));
}
// 静态资源使用缓存优先策略
else if (url.pathname.match(/\.(css|js|png|jpg|jpeg|gif|svg)$/)) {
event.respondWith(CacheStrategyManager.cacheFirst(event.request));
}
});
CDN缓存策略
CDN配置与使用
typescript
// CDN配置管理器
class CDNManager {
private cdnUrl: string;
private fallbackUrl: string;
constructor(cdnUrl: string, fallbackUrl: string) {
this.cdnUrl = cdnUrl;
this.fallbackUrl = fallbackUrl;
}
// 生成CDN URL
generateUrl(path: string): string {
return `${this.cdnUrl}${path}`;
}
// 加载资源
async loadResource(path: string): Promise<string> {
try {
const response = await fetch(this.generateUrl(path));
if (!response.ok) {
throw new Error('CDN resource not available');
}
return await response.text();
} catch (error) {
console.warn('CDN加载失败,使用备用地址:', error);
return this.loadFromFallback(path);
}
}
// 从备用地址加载
private async loadFromFallback(path: string): Promise<string> {
const response = await fetch(`${this.fallbackUrl}${path}`);
return response.text();
}
}
// 使用示例
const cdnManager = new CDNManager(
'https://cdn.example.com',
'https://backup.example.com'
);
// 加载资源
const styles = await cdnManager.loadResource('/styles/main.min.css');
数据缓存策略
前端状态缓存
typescript
// 状态缓存管理器
class StateCache {
private cache: Map<string, any>;
private expirations: Map<string, number>;
constructor() {
this.cache = new Map();
this.expirations = new Map();
}
// 设置缓存
setState(key: string, value: any, ttl?: number): void {
this.cache.set(key, value);
if (ttl) {
this.expirations.set(key, Date.now() + ttl * 1000);
}
}
// 获取缓存
getState(key: string): any {
// 检查是否过期
const expiration = this.expirations.get(key);
if (expiration && Date.now() > expiration) {
this.cache.delete(key);
this.expirations.delete(key);
return null;
}
return this.cache.get(key);
}
// 清除缓存
clearState(key?: string): void {
if (key) {
this.cache.delete(key);
this.expirations.delete(key);
} else {
this.cache.clear();
this.expirations.clear();
}
}
}
// 使用示例
const stateCache = new StateCache();
// 缓存用户数据,有效期1小时
stateCache.setState('currentUser', { id: 1, name: 'John' }, 3600);
// 获取缓存数据
const user = stateCache.getState('currentUser');
API响应缓存
typescript
// API缓存管理器
class APICache {
private cache: Map<string, {
data: any;
timestamp: number;
ttl: number;
}>;
constructor() {
this.cache = new Map();
}
// 生成缓存键
private generateCacheKey(url: string, params?: object): string {
return `${url}:${JSON.stringify(params || {})}`;
}
// 检查缓存是否有效
private isValid(cacheKey: string): boolean {
const cached = this.cache.get(cacheKey);
if (!cached) return false;
return Date.now() - cached.timestamp < cached.ttl * 1000;
}
// 缓存API响应
async cacheApiResponse(
url: string,
params?: object,
ttl: number = 300 // 默认5分钟
): Promise<any> {
const cacheKey = this.generateCacheKey(url, params);
// 检查缓存
if (this.isValid(cacheKey)) {
return this.cache.get(cacheKey)!.data;
}
// 发起API请求
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
});
const data = await response.json();
// 更新缓存
this.cache.set(cacheKey, {
data,
timestamp: Date.now(),
ttl
});
return data;
}
// 清除指定URL的缓存
clearCache(url: string, params?: object): void {
const cacheKey = this.generateCacheKey(url, params);
this.cache.delete(cacheKey);
}
// 清除所有缓存
clearAllCache(): void {
this.cache.clear();
}
}
// 使用示例
const apiCache = new APICache();
// 使用缓存获取数据
const getData = async () => {
try {
const data = await apiCache.cacheApiResponse(
'/api/data',
{ type: 'users' },
600 // 10分钟缓存
);
return data;
} catch (error) {
console.error('数据获取失败:', error);
throw error;
}
};
最佳实践与建议
-
分层缓存策略
- 合理使用不同级别的缓存
- 根据数据特性选择合适的缓存方式
- 实现缓存预热和更新机制
-
缓存失效处理
- 实现优雅的降级策略
- 处理缓存过期和清理
- 监控缓存命中率
-
性能优化
- 避免缓存过大数据量
- 定期清理无用缓存
- 实现缓存预加载
-
安全考虑
- 不缓存敏感数据
- 实现缓存数据加密
- 防止缓存投毒攻击
总结
前端缓存策略是提升应用性能的关键手段。通过合理运用各种缓存机制,可以:
- 减少网络请求
- 提升响应速度
- 改善用户体验
- 降低服务器负载
- 优化资源利用
选择合适的缓存策略需要考虑数据特性、更新频率、安全要求等多个因素,并在实践中不断优化和调整。
学习资源
- MDN Web缓存指南
- Chrome开发者工具文档
- Service Worker教程
- HTTP缓存规范
- 前端性能优化实践
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻