工程化与框架系列(18)--前端缓存策略

前端缓存策略 🗄️

引言

缓存是提升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;
    }
};

最佳实践与建议

  1. 分层缓存策略

    • 合理使用不同级别的缓存
    • 根据数据特性选择合适的缓存方式
    • 实现缓存预热和更新机制
  2. 缓存失效处理

    • 实现优雅的降级策略
    • 处理缓存过期和清理
    • 监控缓存命中率
  3. 性能优化

    • 避免缓存过大数据量
    • 定期清理无用缓存
    • 实现缓存预加载
  4. 安全考虑

    • 不缓存敏感数据
    • 实现缓存数据加密
    • 防止缓存投毒攻击

总结

前端缓存策略是提升应用性能的关键手段。通过合理运用各种缓存机制,可以:

  1. 减少网络请求
  2. 提升响应速度
  3. 改善用户体验
  4. 降低服务器负载
  5. 优化资源利用

选择合适的缓存策略需要考虑数据特性、更新频率、安全要求等多个因素,并在实践中不断优化和调整。

学习资源

  1. MDN Web缓存指南
  2. Chrome开发者工具文档
  3. Service Worker教程
  4. HTTP缓存规范
  5. 前端性能优化实践

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关推荐
哟哟耶耶2 小时前
React-04React组件状态(state),构造器初始化state以及数据读取,添加点击事件并更改state状态值
前端·javascript·react.js
kiramario2 小时前
用IconContext.Provider修改react-icons的icon样式
前端·javascript·react.js
destinyol2 小时前
React首页加载速度优化
前端·javascript·react.js·webpack·前端框架
程序员小续2 小时前
React 多个 HOC 嵌套太深,会带来哪些隐患?
java·前端·javascript·vue.js·python·react.js·webpack
大猫会长3 小时前
用AbortController取消事件绑定
前端
程序员小杰@3 小时前
AI前端组件库Ant DesIgn X
开发语言·前端·人工智能
致微3 小时前
Vue项目 bug 解决
前端·vue.js·bug
慕斯策划一场流浪4 小时前
fastGPT—nextjs—mongoose—团队管理之部门相关api接口实现
前端·javascript·html·fastgpt部门创建·fastgpt团队管理·fastgpt部门成员更新·fastgpt部门成员创建
我自纵横20235 小时前
事件处理程序
开发语言·前端·javascript·css·json·ecmascript
坊钰5 小时前
【MySQL 数据库】数据类型
java·开发语言·前端·数据库·学习·mysql·html