前端缓存技术深度解析:从原理到实践
目录
- 缓存概述
- [HTTP 缓存(浏览器缓存)](#HTTP 缓存(浏览器缓存))
- [2.1 强缓存](#2.1 强缓存)
- [2.2 协商缓存](#2.2 协商缓存)
- [2.3 缓存控制指令](#2.3 缓存控制指令)
- [Service Worker 缓存](#Service Worker 缓存)
- [3.1 Cache Storage](#3.1 Cache Storage)
- [3.2 缓存策略](#3.2 缓存策略)
- [3.3 离线优先](#3.3 离线优先)
- 内存缓存
- [4.1 内存数据结构](#4.1 内存数据结构)
- [4.2 LRU 缓存实现](#4.2 LRU 缓存实现)
- [4.3 内存缓存管理](#4.3 内存缓存管理)
- 状态管理缓存
- [5.1 Redux 缓存](#5.1 Redux 缓存)
- [5.2 Vuex/Pinia 缓存](#5.2 Vuex/Pinia 缓存)
- [5.3 React Query 缓存](#5.3 React Query 缓存)
- 浏览器存储缓存
- [6.1 LocalStorage](#6.1 LocalStorage)
- [6.2 SessionStorage](#6.2 SessionStorage)
- [6.3 IndexedDB](#6.3 IndexedDB)
- 缓存策略详解
- [7.1 Cache First](#7.1 Cache First)
- [7.2 Network First](#7.2 Network First)
- [7.3 Stale While Revalidate](#7.3 Stale While Revalidate)
- [7.4 Cache Only](#7.4 Cache Only)
- [7.5 Network Only](#7.5 Network Only)
- 实际应用场景
- [8.1 API 数据缓存](#8.1 API 数据缓存)
- [8.2 图片资源缓存](#8.2 图片资源缓存)
- [8.3 页面组件缓存](#8.3 页面组件缓存)
- 性能优化实践
- 缓存失效与更新
- 常见问题与解决方案
- 最佳实践
- 总结
缓存概述
什么是缓存
缓存(Cache)是一种存储数据的技术,其核心目标是通过存储数据的副本,在后续请求中直接返回这些副本,从而提高数据访问速度和系统性能。
为什么需要缓存
在 Web 应用中,缓存的重要性不言而喻:
无缓存场景:
用户请求 → 服务器处理 → 数据库查询 → 数据处理 → 响应返回
↑ ↓
慢(多次网络往返 + 复杂计算) 耗时:500-2000ms
有缓存场景:
用户请求 → 缓存检查 → 直接返回
↑ ↓
快(内存访问) 耗时:1-50ms
缓存的优势
- 提升性能:减少网络请求,加快页面加载速度
- 降低服务器负载:减少数据库查询和计算
- 节省带宽:减少数据传输量
- 改善用户体验:离线可用,快速响应
- 降低成本:减少服务器资源消耗
缓存的挑战
- 数据一致性:缓存数据可能与源数据不同步
- 缓存穿透:请求不存在的数据导致缓存失效
- 缓存雪崩:大量缓存同时失效导致请求涌入后端
- 缓存击穿:热点数据失效时大量请求直达数据库
- 存储空间限制:浏览器存储空间有限
缓存层级模型
┌─────────────────────────────────────┐
│ 用户请求 │
└─────────────────┬───────────────────┘
│
┌─────────────────▼───────────────────┐
│ 浏览器缓存 │
│ ┌──────────┐ ┌──────────┐ │
│ │ HTTP │ │ Service │ │
│ │ 缓存 │ │ Worker │ │
│ └──────────┘ └──────────┘ │
└─────────────────┬───────────────────┘
│
┌─────────────────▼───────────────────┐
│ 应用内存缓存 │
│ ┌──────────────────────┐ │
│ │ React/Vue │ │
│ │ 状态缓存 │ │
│ └──────────────────────┘ │
└─────────────────┬───────────────────┘
│
┌─────────────────▼───────────────────┐
│ 服务器缓存 │
│ ┌──────────────────────┐ │
│ │ Redis / Memcached │ │
│ └──────────────────────┘ │
└─────────────────┬───────────────────┘
│
┌─────────────────▼───────────────────┐
│ 数据库缓存 │
│ ┌──────────────────────┐ │
│ │ 查询结果缓存 │ │
│ └──────────────────────┘ │
└─────────────────────────────────────┘
HTTP 缓存(浏览器缓存)
HTTP 缓存是浏览器最基础的缓存机制,通过 HTTP 头信息控制资源的缓存行为。
1. 强缓存
强缓存(Strong Cache)不会向服务器发送请求,直接从缓存中获取资源。
Cache-Control
http
Cache-Control: max-age=3600
常用指令:
max-age=<seconds>:缓存有效期(秒)no-cache:需要验证缓存有效性no-store:完全不使用缓存public:响应可被任何缓存存储private:响应只能被私有缓存存储immutable:资源不会更新
javascript
// Node.js 服务器端设置
app.get('/api/data', (req, res) => {
res.set('Cache-Control', 'public, max-age=3600'); // 缓存1小时
res.json({ data: 'some data' });
});
// 静态资源长期缓存
app.use('/static', express.static('public', {
setHeaders: (res, path) => {
res.set('Cache-Control', 'public, max-age=31536000, immutable');
}
}));
nginx
# Nginx 配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location /api/ {
expires 5m;
add_header Cache-Control "public";
}
Expires
http
Expires: Wed, 21 Oct 2026 07:28:00 GMT
javascript
// 设置过期时间
res.set('Expires', new Date(Date.now() + 3600000).toUTCString());
注意:Expires 是 HTTP/1.0 的产物,Cache-Control 是 HTTP/1.1 的改进,优先级更高。
2. 协商缓存
协商缓存(Conditional Cache)需要向服务器验证资源是否更新。
Last-Modified / If-Modified-Since
http
Last-Modified: Wed, 21 Oct 2026 07:28:00 GMT
javascript
// 服务器端实现
app.get('/api/data', (req, res) => {
const lastModified = new Date('2026-01-05').toUTCString();
const ifModifiedSince = req.get('If-Modified-Since');
if (ifModifiedSince === lastModified) {
return res.status(304).end(); // 未修改
}
res.set('Last-Modified', lastModified);
res.json({ data: 'some data' });
});
ETag / If-None-Match
http
ETag: "abc123"
javascript
// 服务器端实现
const crypto = require('crypto');
app.get('/api/data', (req, res) => {
const data = JSON.stringify({ data: 'some data' });
const etag = crypto.createHash('md5').update(data).digest('hex');
const ifNoneMatch = req.get('If-None-Match');
if (ifNoneMatch === etag) {
return res.status(304).end();
}
res.set('ETag', etag);
res.json(JSON.parse(data));
});
ETag vs Last-Modified:
| 特性 | ETag | Last-Modified |
|---|---|---|
| 精度 | 基于内容哈希 | 基于时间戳 |
| 准确性 | 更高 | 可能不准确(秒级) |
| 优先级 | 更高 | 较低 |
| 支持度 | 现代浏览器广泛支持 | 所有浏览器支持 |
3. 缓存控制指令
常用指令组合
javascript
// 不缓存
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
// 短时间缓存
res.set('Cache-Control', 'public, max-age=300'); // 5分钟
// 长时间缓存(静态资源)
res.set('Cache-Control', 'public, max-age=31536000, immutable');
// 私有缓存(敏感数据)
res.set('Cache-Control', 'private, max-age=3600');
// 代理缓存
res.set('Cache-Control', 'public, s-maxage=3600');
Vary 头
http
Vary: Accept-Encoding
javascript
// 根据 Accept-Encoding 缓存不同版本
res.set('Vary', 'Accept-Encoding, User-Agent');
Service Worker 缓存
Service Worker 是在浏览器后台运行的脚本,提供了强大的缓存控制能力。
1. Cache Storage
javascript
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration);
});
}
javascript
// sw.js - Service Worker 文件
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/static/js/main.js',
'/static/css/main.css',
'/api/user-data'
];
// 安装阶段 - 缓存资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// 激活阶段 - 清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});
// 拦截请求
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 如果缓存中有,直接返回
if (response) {
return response;
}
// 否则请求网络
return fetch(event.request);
}
)
);
});
2. 缓存策略
Cache First(缓存优先)
javascript
self.addEventListener('fetch', event => {
// 只缓存 GET 请求
if (event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
}
return fetch(event.request)
.then(response => {
// 检查响应是否有效
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应并缓存
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
Network First(网络优先)
javascript
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
event.respondWith(
fetch(event.request)
.then(response => {
// 网络请求成功,缓存响应
const responseClone = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
// 网络请求失败,使用缓存
return caches.match(event.request);
})
);
});
Stale While Revalidate(过期数据可用,同时后台更新)
javascript
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
const networkFetch = fetch(event.request)
.then(response => {
// 更新缓存
const responseClone = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
// 网络请求失败
console.error('Network fetch failed');
});
// 立即返回缓存,同时后台更新
return cachedResponse || networkFetch;
})
);
});
3. 离线优先
javascript
// IndexedDB 辅助类
class IDBHelper {
constructor(dbName, storeName) {
this.dbName = dbName;
this.storeName = storeName;
this.db = null;
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
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(this.storeName)) {
db.createObjectStore(this.storeName, { keyPath: 'id' });
}
};
});
}
async get(key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
}
async set(value) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.put(value);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
}
}
// 使用 IndexedDB 缓存数据
const idbHelper = new IDBHelper('MyAppDB', 'apiCache');
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(
(async () => {
try {
// 尝试网络请求
const networkResponse = await fetch(event.request);
// 缓存响应
if (networkResponse.ok) {
await idbHelper.set({
id: event.request.url,
data: await networkResponse.clone().json(),
timestamp: Date.now()
});
}
return networkResponse;
} catch (error) {
// 网络失败,尝试缓存
const cached = await idbHelper.get(event.request.url);
if (cached) {
return new Response(JSON.stringify(cached.data), {
headers: { 'Content-Type': 'application/json' }
});
}
// 没有缓存,返回离线页面
return new Response('Offline', { status: 503 });
}
})()
);
}
});
内存缓存
内存缓存是在应用运行期间将数据存储在内存中的缓存方式。
1. 内存数据结构
Map 缓存
javascript
class SimpleCache {
constructor() {
this.cache = new Map();
}
set(key, value, ttl = 0) {
const expires = ttl > 0 ? Date.now() + ttl : 0;
this.cache.set(key, { value, expires });
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
// 检查是否过期
if (item.expires && item.expires < Date.now()) {
this.cache.delete(key);
return null;
}
return item.value;
}
delete(key) {
this.cache.delete(key);
}
clear() {
this.cache.clear();
}
// 获取缓存大小
size() {
return this.cache.size;
}
// 清理过期项
cleanup() {
const now = Date.now();
for (const [key, item] of this.cache.entries()) {
if (item.expires && item.expires < now) {
this.cache.delete(key);
}
}
}
}
// 使用示例
const cache = new SimpleCache();
// 缓存用户信息,10分钟过期
cache.set('user:123', { name: '张三', age: 25 }, 10 * 60 * 1000);
const user = cache.get('user:123');
console.log(user); // { name: '张三', age: 25 }
WeakMap 缓存
javascript
class WeakRefCache {
constructor() {
this.cache = new WeakMap();
this.timeouts = new WeakMap();
}
set(key, value, ttl = 0) {
this.cache.set(key, { value, timestamp: Date.now() });
if (ttl > 0) {
const timeoutId = setTimeout(() => {
this.delete(key);
}, ttl);
this.timeouts.set(key, timeoutId);
}
}
get(key) {
const item = this.cache.get(key);
return item ? item.value : null;
}
delete(key) {
this.cache.delete(key);
const timeoutId = this.timeouts.get(key);
if (timeoutId) {
clearTimeout(timeoutId);
this.timeouts.delete(key);
}
}
}
// 使用示例
const weakCache = new WeakRefCache();
// 缓存 DOM 元素相关数据
const element = document.querySelector('#myElement');
weakCache.set(element, { data: 'some data' }, 5 * 60 * 1000);
2. LRU 缓存实现
javascript
class LRUCache {
constructor(limit = 100) {
this.limit = limit;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return null;
const value = this.cache.get(key).value;
// 将访问的项移到末尾(最近使用)
this.cache.delete(key);
this.cache.set(key, { value, timestamp: Date.now() });
return value;
}
set(key, value, ttl = 0) {
// 检查是否已存在
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.limit) {
// 删除最久未使用的项(第一个)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
const expires = ttl > 0 ? Date.now() + ttl : 0;
this.cache.set(key, { value, expires, timestamp: Date.now() });
}
has(key) {
const item = this.cache.get(key);
if (!item) return false;
if (item.expires && item.expires < Date.now()) {
this.cache.delete(key);
return false;
}
return true;
}
delete(key) {
this.cache.delete(key);
}
clear() {
this.cache.clear();
}
// 获取所有有效项
getValidItems() {
const now = Date.now();
const validItems = [];
for (const [key, item] of this.cache.entries()) {
if (!item.expires || item.expires >= now) {
validItems.push({ key, value: item.value });
} else {
this.cache.delete(key);
}
}
return validItems;
}
}
// 使用示例
const lruCache = new LRUCache(50);
// 缓存 API 响应
async function fetchWithCache(url, ttl = 5 * 60 * 1000) {
if (lruCache.has(url)) {
console.log('使用缓存:', url);
return lruCache.get(url);
}
const response = await fetch(url);
const data = await response.json();
lruCache.set(url, data, ttl);
return data;
}
3. 内存缓存管理
缓存管理器
javascript
class CacheManager {
constructor() {
this.caches = new Map();
this.cleanupInterval = null;
}
createCache(name, options = {}) {
const { limit = 100, ttl = 0 } = options;
if (this.caches.has(name)) {
return this.caches.get(name);
}
const cache = new LRUCache(limit);
const cacheWrapper = {
name,
set: (key, value) => cache.set(key, value, ttl),
get: (key) => cache.get(key),
has: (key) => cache.has(key),
delete: (key) => cache.delete(key),
clear: () => cache.clear(),
size: () => cache.cache.size,
getStats: () => ({
name,
size: cache.cache.size,
limit,
ttl
})
};
this.caches.set(name, cacheWrapper);
return cacheWrapper;
}
getCache(name) {
return this.caches.get(name);
}
deleteCache(name) {
const cache = this.caches.get(name);
if (cache) {
cache.clear();
this.caches.delete(name);
}
}
clearAll() {
for (const cache of this.caches.values()) {
cache.clear();
}
}
getAllStats() {
const stats = [];
for (const cache of this.caches.values()) {
stats.push(cache.getStats());
}
return stats;
}
// 启动定期清理
startCleanup(interval = 60000) {
if (this.cleanupInterval) return;
this.cleanupInterval = setInterval(() => {
for (const cache of this.caches.values()) {
const validItems = cache.getValidItems?.();
if (validItems) {
console.log(`清理缓存 ${cache.name},剩余有效项: ${validItems.length}`);
}
}
}, interval);
}
stopCleanup() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
}
}
// 全局缓存管理器实例
const cacheManager = new CacheManager();
// 创建不同类型的缓存
const apiCache = cacheManager.createCache('api', { limit: 200, ttl: 300000 });
const componentCache = cacheManager.createCache('component', { limit: 50, ttl: 600000 });
const userCache = cacheManager.createCache('user', { limit: 100, ttl: 600000 });
// 启动定期清理
cacheManager.startCleanup();
// 在页面卸载时清理
window.addEventListener('beforeunload', () => {
cacheManager.stopCleanup();
});
状态管理缓存
1. Redux 缓存
javascript
// 使用 redux-persist 持久化状态
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
// 配置持久化
const persistConfig = {
key: 'root',
storage,
whitelist: ['user', 'settings'], // 只持久化这些 reducer
blacklist: ['temp'] // 不持久化这些 reducer
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
}
})
});
const persistor = persistStore(store);
// 在组件中使用
import { useSelector } from 'react-redux';
function UserProfile() {
const user = useSelector((state) => state.user.data);
const isLoading = useSelector((state) => state.user.loading);
if (isLoading) return <div>加载中...</div>;
return <div>{user.name}</div>;
}
自定义中间件缓存
javascript
// API 缓存中间件
const apiCacheMiddleware = (store) => (next) => (action) => {
const { type, payload } = action;
// 如果是 API 请求
if (type.startsWith('api/')) {
const cacheKey = JSON.stringify(payload);
const cachedData = getFromCache(cacheKey);
if (cachedData && !payload.forceRefresh) {
// 返回缓存数据
return { type: `${type}_SUCCESS`, payload: cachedData };
}
}
const result = next(action);
// 如果请求成功,缓存结果
if (type.endsWith('_SUCCESS')) {
const { url, ttl = 300000 } = payload;
setCache(url, action.payload, ttl);
}
return result;
};
// 使用缓存中间件
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiCacheMiddleware)
});
2. Vuex/Pinia 缓存
javascript
// Pinia 缓存插件
export function cachePlugin({ store }) {
// 从 localStorage 恢复状态
const savedState = localStorage.getItem('pinia-state');
if (savedState) {
store.$patch(JSON.parse(savedState));
}
// 状态变化时保存到 localStorage
store.$subscribe((mutation, state) => {
localStorage.setItem('pinia-state', JSON.stringify(state));
});
}
// main.js
import { createPinia } from 'pinia';
import { cachePlugin } from './plugins/cache';
const pinia = createPinia();
pinia.use(cachePlugin);
app.use(pinia);
Pinia 缓存 Store
javascript
// stores/cache.js
import { defineStore } from 'pinia';
import axios from 'axios';
export const useCacheStore = defineStore('cache', {
state: () => ({
_cache: new Map(),
_timestamps: new Map()
}),
actions: {
async get(key, fetcher, ttl = 300000) {
// 检查缓存是否存在且未过期
if (this._cache.has(key)) {
const timestamp = this._timestamps.get(key);
if (Date.now() - timestamp < ttl) {
return this._cache.get(key);
}
}
// 获取新数据
const data = await fetcher();
this._cache.set(key, data);
this._timestamps.set(key, Date.now());
return data;
},
set(key, value) {
this._cache.set(key, value);
this._timestamps.set(key, Date.now());
},
delete(key) {
this._cache.delete(key);
this._timestamps.delete(key);
},
clear() {
this._cache.clear();
this._timestamps.clear();
}
}
});
// 使用缓存 Store
const cacheStore = useCacheStore();
const userData = await cacheStore.get('user:123', async () => {
const response = await axios.get('/api/user/123');
return response.data;
});
3. React Query 缓存
javascript
// React Query 配置
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5分钟内数据新鲜
cacheTime: 10 * 60 * 1000, // 10分钟后清理缓存
retry: 3,
refetchOnWindowFocus: false
}
}
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserProfile />
</QueryClientProvider>
);
}
// 使用 React Query
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery(
['user', userId],
async () => {
const response = await fetch(`/api/user/${userId}`);
if (!response.ok) throw new Error('Network error');
return response.json();
},
{
staleTime: 5 * 60 * 1000, // 缓存5分钟
cacheTime: 10 * 60 * 1000,
enabled: !!userId // 只有 userId 存在时才执行查询
}
);
if (isLoading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return <div>{data.name}</div>;
}
// 手动操作缓存
function updateUser() {
queryClient.setQueryData(['user', userId], (oldData) => ({
...oldData,
name: '新名称'
}));
// 失效缓存
queryClient.invalidateQueries(['user']);
}
浏览器存储缓存
1. LocalStorage
javascript
// LocalStorage 封装类
class LocalStorageCache {
constructor(prefix = 'cache_') {
this.prefix = prefix;
}
set(key, value, ttl = 0) {
const data = {
value,
timestamp: Date.now(),
ttl: ttl > 0 ? ttl : null
};
localStorage.setItem(this.prefix + key, JSON.stringify(data));
}
get(key) {
const item = localStorage.getItem(this.prefix + key);
if (!item) return null;
try {
const data = JSON.parse(item);
// 检查是否过期
if (data.ttl && Date.now() - data.timestamp > data.ttl) {
this.delete(key);
return null;
}
return data.value;
} catch (error) {
console.error('Parse error:', error);
this.delete(key);
return null;
}
}
delete(key) {
localStorage.removeItem(this.prefix + key);
}
clear() {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
localStorage.removeItem(key);
}
});
}
// 获取所有缓存键
getAllKeys() {
return Object.keys(localStorage)
.filter(key => key.startsWith(this.prefix))
.map(key => key.replace(this.prefix, ''));
}
}
// 使用示例
const localCache = new LocalStorageCache();
// 缓存用户设置
localCache.set('userSettings', {
theme: 'dark',
language: 'zh-CN'
}, 24 * 60 * 60 * 1000); // 24小时
const settings = localCache.get('userSettings');
console.log(settings); // { theme: 'dark', language: 'zh-CN' }
2. SessionStorage
javascript
// SessionStorage 缓存
class SessionStorageCache {
constructor(prefix = 'session_') {
this.prefix = prefix;
}
set(key, value) {
const data = {
value,
timestamp: Date.now()
};
sessionStorage.setItem(this.prefix + key, JSON.stringify(data));
}
get(key) {
const item = sessionStorage.getItem(this.prefix + key);
if (!item) return null;
try {
const data = JSON.parse(item);
return data.value;
} catch (error) {
console.error('Parse error:', error);
this.delete(key);
return null;
}
}
delete(key) {
sessionStorage.removeItem(this.prefix + key);
}
clear() {
const keys = Object.keys(sessionStorage);
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
sessionStorage.removeItem(key);
}
});
}
}
// 使用示例
const sessionCache = new SessionStorageCache();
// 缓存表单数据
sessionCache.set('formData', {
name: '张三',
email: 'zhangsan@example.com'
});
// 页面刷新后数据仍然存在(会话期间)
const formData = sessionCache.get('formData');
3. IndexedDB
javascript
// IndexedDB 封装
class IndexedDBCache {
constructor(dbName, storeName) {
this.dbName = dbName;
this.storeName = storeName;
this.db = null;
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
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(this.storeName)) {
const store = db.createObjectStore(this.storeName, {
keyPath: 'key'
});
store.createIndex('timestamp', 'timestamp', { unique: false });
}
};
});
}
async set(key, value, ttl = 0) {
const data = {
key,
value,
timestamp: Date.now(),
ttl
};
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.put(data);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
}
async get(key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const data = request.result;
if (!data) {
resolve(null);
return;
}
// 检查是否过期
if (data.ttl && Date.now() - data.timestamp > data.ttl) {
this.delete(key);
resolve(null);
return;
}
resolve(data.value);
};
});
}
async delete(key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.delete(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
}
async clear() {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.clear();
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
}
async getAll() {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAll();
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
}
}
// 使用示例
const idbCache = new IndexedDBCache('MyApp', 'apiCache');
await idbCache.init();
// 缓存大量数据
await idbCache.set('user:123', {
profile: { name: '张三', age: 25 },
posts: [...], // 大数组
preferences: {...}
}, 3600000); // 1小时
const userData = await idbCache.get('user:123');
缓存策略详解
1. Cache First
javascript
// Cache First 策略:先查缓存,缓存没有再请求网络
async function cacheFirst(request, cacheName) {
const cache = await caches.open(cacheName);
const cachedResponse = await cache.match(request);
if (cachedResponse) {
return cachedResponse;
}
const networkResponse = await fetch(request);
if (networkResponse.ok) {
cache.put(request, networkResponse.clone());
}
return networkResponse;
}
// 适用场景:静态资源(CSS、JS、图片)
const cssResponse = await cacheFirst('/static/css/main.css', 'assets-cache');
2. Network First
javascript
// Network First 策略:先请求网络,失败则使用缓存
async function networkFirst(request, cacheName, timeout = 5000) {
const cache = await caches.open(cacheName);
try {
// 设置超时
const networkPromise = fetch(request);
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Timeout')), timeout);
});
const response = await Promise.race([networkPromise, timeoutPromise]);
if (response.ok) {
cache.put(request, response.clone());
}
return response;
} catch (error) {
// 网络失败,使用缓存
const cachedResponse = await cache.match(request);
if (cachedResponse) {
return cachedResponse;
}
throw error;
}
}
// 适用场景:API 数据
const userData = await networkFirst('/api/user/123', 'api-cache');
3. Stale While Revalidate
javascript
// Stale While Revalidate 策略:返回缓存,同时后台更新
async function staleWhileRevalidate(request, cacheName) {
const cache = await caches.open(cacheName);
const cachedResponse = await cache.match(request);
const fetchPromise = fetch(request)
.then(response => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
})
.catch(error => {
console.error('Fetch failed:', error);
return null;
});
// 立即返回缓存,同时后台更新
return cachedResponse || fetchPromise;
}
// 适用场景:新闻文章、博客内容
const articles = await staleWhileRevalidate('/api/articles', 'articles-cache');
4. Cache Only
javascript
// Cache Only 策略:只从缓存获取
async function cacheOnly(request, cacheName) {
const cache = await caches.open(cacheName);
const cachedResponse = await cache.match(request);
if (!cachedResponse) {
throw new Error('No cached response found');
}
return cachedResponse;
}
// 适用场景:离线页面
const offlinePage = await cacheOnly('/offline.html', 'offline-cache');
5. Network Only
javascript
// Network Only 策略:只从网络获取
async function networkOnly(request) {
return await fetch(request);
}
// 适用场景:登录、支付等敏感操作
const loginResponse = await networkOnly('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
策略选择决策树
需要缓存的资源
|
├─ 静态资源(CSS、JS、图片) → Cache First
│ └─ 长期缓存(1年)+ 版本控制
│
├─ API 数据(用户信息、商品列表) → Network First
│ └─ 短期缓存(5分钟)+ 错误回退
│
├─ 频繁更新的内容(新闻、博客) → Stale While Revalidate
│ └─ 显示缓存 + 后台更新
│
├─ 离线页面 → Cache Only
│ └─ 预缓存关键页面
│
└─ 敏感操作(登录、支付) → Network Only
└─ 不缓存,确保数据最新
实际应用场景
1. API 数据缓存
用户信息缓存
javascript
class UserCache {
constructor() {
this.cache = new LRUCache(50);
this.apiCache = new LocalStorageCache('user_');
}
async getUser(userId, forceRefresh = false) {
const cacheKey = `user:${userId}`;
// 强制刷新或内存缓存中没有
if (!forceRefresh) {
const cached = this.cache.get(cacheKey);
if (cached) {
console.log('从内存缓存获取用户:', userId);
return cached;
}
}
// 检查本地存储缓存
const localCached = this.apiCache.get(cacheKey);
if (localCached && !forceRefresh) {
console.log('从本地存储获取用户:', userId);
this.cache.set(cacheKey, localCached);
return localCached;
}
// 请求 API
try {
const response = await fetch(`/api/user/${userId}`);
if (!response.ok) throw new Error('Failed to fetch');
const userData = await response.json();
// 更新缓存
this.cache.set(cacheKey, userData);
this.apiCache.set(cacheKey, userData, 10 * 60 * 1000); // 10分钟
return userData;
} catch (error) {
console.error('Failed to fetch user:', error);
// 返回本地缓存(如果有)
if (localCached) {
return localCached;
}
throw error;
}
}
updateUser(userId, userData) {
const cacheKey = `user:${userId}`;
this.cache.set(cacheKey, userData);
this.apiCache.set(cacheKey, userData, 10 * 60 * 1000);
}
clearUser(userId) {
const cacheKey = `user:${userId}`;
this.cache.delete(cacheKey);
this.apiCache.delete(cacheKey);
}
}
const userCache = new UserCache();
// 使用
const user = await userCache.getUser('123');
userCache.updateUser('123', { ...user, name: '新名称' });
分页数据缓存
javascript
class PaginatedCache {
constructor() {
this.cache = new Map(); // key: `${url}:${page}`, value: { data, timestamp }
this.cacheTime = 5 * 60 * 1000; // 5分钟
}
async getPage(url, page, params = {}) {
const cacheKey = `${url}:${page}:${JSON.stringify(params)}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTime) {
return cached.data;
}
const queryParams = new URLSearchParams({ page, ...params });
const response = await fetch(`${url}?${queryParams}`);
if (!response.ok) throw new Error('Failed to fetch');
const data = await response.json();
this.cache.set(cacheKey, {
data,
timestamp: Date.now()
});
return data;
}
invalidatePage(url, page) {
// 删除指定页的缓存
for (const key of this.cache.keys()) {
if (key.startsWith(`${url}:${page}:`)) {
this.cache.delete(key);
}
}
}
invalidateUrl(url) {
// 删除指定 URL 的所有缓存
for (const key of this.cache.keys()) {
if (key.startsWith(`${url}:`)) {
this.cache.delete(key);
}
}
}
}
const paginatedCache = new PaginatedCache();
// 使用
const products = await paginatedCache.getPage('/api/products', 1, {
category: 'electronics',
sort: 'price'
});
2. 图片资源缓存
图片预加载
javascript
class ImageCache {
constructor(maxCache = 50) {
this.cache = new Map(); // key: url, value: { img, timestamp }
this.maxCache = maxCache;
}
preloadImage(url) {
return new Promise((resolve, reject) => {
// 检查缓存
const cached = this.cache.get(url);
if (cached) {
resolve(cached.img);
return;
}
const img = new Image();
img.onload = () => {
// 添加到缓存
this.cache.set(url, {
img,
timestamp: Date.now()
});
// 如果缓存超过限制,删除最旧的
if (this.cache.size > this.maxCache) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
resolve(img);
};
img.onerror = reject;
img.src = url;
});
}
getImage(url) {
const cached = this.cache.get(url);
return cached ? cached.img : null;
}
clear() {
this.cache.clear();
}
}
const imageCache = new ImageCache();
// 预加载图片
function preloadImages(urls) {
return Promise.all(urls.map(url => imageCache.preloadImage(url)));
}
// 使用
preloadImages([
'/images/hero.jpg',
'/images/product1.jpg',
'/images/product2.jpg'
]).then(() => {
console.log('所有图片预加载完成');
});
图片懒加载
javascript
class LazyImageLoader {
constructor() {
this.imageCache = new ImageCache(100);
this.observer = null;
this.init();
}
init() {
// Intersection Observer 实现懒加载
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.dataset.src;
this.loadImage(img, src);
this.observer.unobserve(img);
}
});
}, {
rootMargin: '50px' // 提前50px开始加载
});
}
}
async loadImage(img, src) {
try {
const image = await this.imageCache.preloadImage(src);
img.src = image.src;
img.classList.remove('lazy');
} catch (error) {
console.error('Failed to load image:', error);
img.classList.add('error');
}
}
observe(img) {
if (this.observer) {
this.observer.observe(img);
} else {
// 降级方案:直接加载
const src = img.dataset.src;
this.loadImage(img, src);
}
}
}
const lazyImageLoader = new LazyImageLoader();
// HTML
// <img class="lazy" data-src="/images/large-image.jpg" alt="示例">
// <img class="lazy" data-src="/images/another-image.jpg" alt="示例">
// JavaScript
document.querySelectorAll('img.lazy').forEach(img => {
lazyImageLoader.observe(img);
});
3. 页面组件缓存
React 组件缓存
javascript
// 使用 React.memo 和 useMemo 缓存组件
const ExpensiveComponent = React.memo(({ data, filter }) => {
const filteredData = useMemo(() => {
return data.filter(item => item.category === filter);
}, [data, filter]);
return (
<div>
{filteredData.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
});
// 使用 useCallback 缓存函数
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<button onClick={handleClick}>点击 {count} 次</button>
<ExpensiveComponent data={expensiveData} filter="active" />
</div>
);
}
动态组件缓存
javascript
class ComponentCache {
constructor() {
this.cache = new Map();
}
async loadComponent(importFunc) {
const modulePath = importFunc.toString();
// 检查缓存
if (this.cache.has(modulePath)) {
return this.cache.get(modulePath);
}
// 动态加载组件
const module = await importFunc();
const Component = module.default;
// 缓存组件
this.cache.set(modulePath, Component);
return Component;
}
clear() {
this.cache.clear();
}
}
const componentCache = new ComponentCache();
// 动态加载路由组件
const routes = [
{
path: '/user',
component: () => import('./UserComponent')
},
{
path: '/product',
component: () => import('./ProductComponent')
}
];
// 渲染路由
function renderRoute(route) {
componentCache.loadComponent(route.component)
.then(Component => {
ReactDOM.render(<Component />, document.getElementById('root'));
});
}
性能优化实践
1. 缓存分层策略
javascript
// 多层缓存架构
class MultiLayerCache {
constructor() {
this.l1Cache = new Map(); // 内存缓存,最快
this.l2Cache = new LocalStorageCache('l2_'); // 本地存储
this.l3Cache = new IndexedDBCache('MyApp', 'l3'); // IndexedDB
}
async get(key) {
// L1 缓存
if (this.l1Cache.has(key)) {
return this.l1Cache.get(key);
}
// L2 缓存
const l2Data = this.l2Cache.get(key);
if (l2Data) {
this.l1Cache.set(key, l2Data);
return l2Data;
}
// L3 缓存
const l3Data = await this.l3Cache.get(key);
if (l3Data) {
this.l1Cache.set(key, l3Data);
this.l2Cache.set(key, l3Data);
return l3Data;
}
return null;
}
async set(key, value, ttl = 0) {
// 设置所有层级
this.l1Cache.set(key, value);
this.l2Cache.set(key, value, ttl);
await this.l3Cache.set(key, value, ttl);
}
async delete(key) {
this.l1Cache.delete(key);
this.l2Cache.delete(key);
await this.l3Cache.delete(key);
}
clear() {
this.l1Cache.clear();
this.l2Cache.clear();
this.l3Cache.clear();
}
}
const multiLayerCache = new MultiLayerCache();
2. 缓存预热
javascript
// 缓存预热
class CacheWarmer {
constructor(cache) {
this.cache = cache;
}
async warmUp(config) {
console.log('开始缓存预热...');
const promises = config.map(async ({ key, fetcher, ttl }) => {
try {
const data = await fetcher();
await this.cache.set(key, data, ttl);
console.log(`预热成功: ${key}`);
} catch (error) {
console.error(`预热失败: ${key}`, error);
}
});
await Promise.all(promises);
console.log('缓存预热完成');
}
}
// 配置预热数据
const warmer = new CacheWarmer(multiLayerCache);
warmer.warmUp([
{
key: 'user:123',
fetcher: () => fetch('/api/user/123').then(r => r.json()),
ttl: 10 * 60 * 1000
},
{
key: 'menu-items',
fetcher: () => fetch('/api/menu').then(r => r.json()),
ttl: 30 * 60 * 1000
},
{
key: 'config',
fetcher: () => fetch('/api/config').then(r => r.json()),
ttl: 60 * 60 * 1000
}
]);
3. 缓存压缩
javascript
// 缓存数据压缩
import LZString from 'lz-string';
class CompressedCache {
constructor(cache) {
this.cache = cache;
}
async set(key, value, ttl = 0) {
// 压缩数据
const compressed = LZString.compress(JSON.stringify(value));
await this.cache.set(key, compressed, ttl);
}
async get(key) {
const compressed = await this.cache.get(key);
if (!compressed) return null;
try {
const decompressed = LZString.decompress(compressed);
return JSON.parse(decompressed);
} catch (error) {
console.error('Failed to decompress:', error);
return null;
}
}
}
const compressedCache = new CompressedCache(multiLayerCache);
4. 缓存统计
javascript
// 缓存统计
class CacheStats {
constructor() {
this.hits = 0;
this.misses = 0;
this.size = 0;
}
recordHit() {
this.hits++;
}
recordMiss() {
this.misses++;
}
get hitRate() {
const total = this.hits + this.misses;
return total > 0 ? (this.hits / total) * 100 : 0;
}
getStats() {
const total = this.hits + this.misses;
return {
hits: this.hits,
misses: this.misses,
total,
hitRate: this.hitRate.toFixed(2) + '%',
size: this.size
};
}
reset() {
this.hits = 0;
this.misses = 0;
}
}
// 带统计的缓存
class StatsCache {
constructor(cache) {
this.cache = cache;
this.stats = new CacheStats();
}
async get(key) {
const value = await this.cache.get(key);
if (value) {
this.stats.recordHit();
} else {
this.stats.recordMiss();
}
return value;
}
async set(key, value, ttl) {
return this.cache.set(key, value, ttl);
}
getStats() {
return this.stats.getStats();
}
resetStats() {
this.stats.reset();
}
}
const statsCache = new StatsCache(multiLayerCache);
// 定期输出统计信息
setInterval(() => {
console.log('缓存统计:', statsCache.getStats());
}, 60000);
缓存失效与更新
1. TTL(Time To Live)
javascript
// TTL 管理
class TTLCache {
constructor() {
this.cache = new Map();
this.timeouts = new Map();
}
set(key, value, ttl) {
// 设置缓存
this.cache.set(key, value);
// 清除之前的定时器
if (this.timeouts.has(key)) {
clearTimeout(this.timeouts.get(key));
}
// 设置新的定时器
if (ttl > 0) {
const timeoutId = setTimeout(() => {
this.delete(key);
}, ttl);
this.timeouts.set(key, timeoutId);
}
}
get(key) {
return this.cache.get(key);
}
delete(key) {
this.cache.delete(key);
const timeoutId = this.timeouts.get(key);
if (timeoutId) {
clearTimeout(timeoutId);
this.timeouts.delete(key);
}
}
clear() {
// 清除所有定时器
for (const timeoutId of this.timeouts.values()) {
clearTimeout(timeoutId);
}
this.timeouts.clear();
this.cache.clear();
}
}
2. 版本控制
javascript
// 基于版本的缓存失效
class VersionedCache {
constructor() {
this.cache = new Map();
this.version = 'v1';
}
setVersion(newVersion) {
if (this.version !== newVersion) {
this.version = newVersion;
this.clear(); // 版本变化时清除所有缓存
}
}
set(key, value, ttl = 0) {
const versionedKey = `${this.version}:${key}`;
this.cache.set(versionedKey, {
value,
ttl,
timestamp: Date.now()
});
}
get(key) {
const versionedKey = `${this.version}:${key}`;
const item = this.cache.get(versionedKey);
if (!item) return null;
if (item.ttl && Date.now() - item.timestamp > item.ttl) {
this.delete(key);
return null;
}
return item.value;
}
delete(key) {
const versionedKey = `${this.version}:${key}`;
this.cache.delete(versionedKey);
}
clear() {
this.cache.clear();
}
}
// 使用版本控制
const versionedCache = new VersionedCache();
// 应用更新时升级版本
versionedCache.setVersion('v2');
3. 基于依赖的失效
javascript
// 基于依赖图的缓存失效
class DependencyCache {
constructor() {
this.cache = new Map();
this.dependents = new Map(); // key -> Set<dependent keys>
}
set(key, value, dependencies = []) {
// 设置缓存
this.cache.set(key, value);
// 更新依赖关系
dependencies.forEach(dep => {
if (!this.dependents.has(dep)) {
this.dependents.set(dep, new Set());
}
this.dependents.get(dep).add(key);
});
}
invalidate(key) {
// 删除缓存
this.cache.delete(key);
// 删除依赖它的缓存
const deps = this.dependents.get(key);
if (deps) {
deps.forEach(depKey => {
this.cache.delete(depKey);
});
this.dependents.delete(key);
}
}
get(key) {
return this.cache.get(key);
}
}
// 使用示例
const depCache = new DependencyCache();
// 设置缓存和依赖关系
depCache.set('user:123', { name: '张三' }, ['settings:123']);
depCache.set('settings:123', { theme: 'dark' });
// settings 变化时,user:123 也会失效
depCache.invalidate('settings:123');
4. 手动失效
javascript
// 手动失效策略
class CacheInvalidator {
constructor() {
this.patterns = new Map(); // pattern -> handler
}
// 注册失效模式
register(pattern, handler) {
this.patterns.set(pattern, handler);
}
// 失效缓存
async invalidate(cacheKey) {
for (const [pattern, handler] of this.patterns.entries()) {
if (this.matchPattern(pattern, cacheKey)) {
await handler(cacheKey);
}
}
}
matchPattern(pattern, key) {
// 简单的 glob 模式匹配
const regex = new RegExp(pattern.replace('*', '.*'));
return regex.test(key);
}
}
// 配置失效规则
const invalidator = new CacheInvalidator();
invalidator.register('user:*', (key) => {
console.log('失效用户缓存:', key);
cache.delete(key);
});
invalidator.register('product:*', (key) => {
console.log('失效商品缓存:', key);
cache.delete(key);
});
// 使用
await invalidator.invalidate('user:123');
常见问题与解决方案
1. 缓存穿透
javascript
// 问题:请求不存在的数据,导致缓存失效
// 解决:缓存空值或使用布隆过滤器
class AntiPenetrationCache {
constructor(cache) {
this.cache = cache;
this.nullCache = new Set(); // 缓存空值的 key
this.nullTTL = 5 * 60 * 1000; // 空值缓存5分钟
}
async get(key) {
// 检查是否是已知不存在的 key
if (this.nullCache.has(key)) {
return null;
}
const value = await this.cache.get(key);
// 如果数据不存在,缓存空值
if (value === null) {
this.nullCache.add(key);
setTimeout(() => {
this.nullCache.delete(key);
}, this.nullTTL);
}
return value;
}
async set(key, value) {
if (value === null) {
this.nullCache.add(key);
} else {
this.nullCache.delete(key);
}
await this.cache.set(key, value);
}
}
2. 缓存雪崩
javascript
// 问题:大量缓存同时失效
// 解决:添加随机延迟、使用分布式锁
class AvalancheProtectionCache {
constructor(cache) {
this.cache = cache;
this.locks = new Set();
}
async get(key, fetcher, ttl = 300000) {
const value = await this.cache.get(key);
if (value !== null) {
return value;
}
// 检查是否已经有其他请求在加载
if (this.locks.has(key)) {
// 等待其他请求完成
return new Promise((resolve) => {
const checkLock = setInterval(() => {
if (!this.locks.has(key)) {
clearInterval(checkLock);
this.get(key, fetcher, ttl).then(resolve);
}
}, 100);
});
}
// 获取分布式锁
this.locks.add(key);
try {
const data = await fetcher();
// 添加随机 TTL 避免同时失效
const randomTTL = ttl + Math.random() * ttl * 0.1; // 最多额外10%
await this.cache.set(key, data, randomTTL);
return data;
} finally {
this.locks.delete(key);
}
}
}
3. 缓存击穿
javascript
// 问题:热点数据失效时大量请求直达数据库
// 解决:互斥锁
class CacheBreakdownProtection {
constructor(cache) {
this.cache = cache;
this.locks = new Map();
}
async get(key, fetcher, ttl = 300000) {
let value = await this.cache.get(key);
if (value !== null) {
return value;
}
// 获取锁(使用分布式锁更好)
const lockKey = `lock:${key}`;
const lockValue = Date.now().toString();
if (this.locks.has(lockKey)) {
// 等待锁释放
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.locks.delete(lockKey);
reject(new Error('Lock timeout'));
}, 5000);
const checkLock = setInterval(() => {
if (!this.locks.has(lockKey)) {
clearInterval(checkLock);
clearTimeout(timeout);
this.get(key, fetcher, ttl).then(resolve).catch(reject);
}
}, 100);
});
}
// 加锁
this.locks.set(lockKey, lockValue);
try {
const data = await fetcher();
await this.cache.set(key, data, ttl);
return data;
} finally {
// 释放锁
this.locks.delete(lockKey);
}
}
}
4. 数据一致性
javascript
// 数据一致性保证
class ConsistentCache {
constructor(cache) {
this.cache = cache;
this.versionMap = new Map(); // key -> version
}
async set(key, value, ttl = 0) {
const version = Date.now();
const data = { value, version };
await this.cache.set(key, data, ttl);
this.versionMap.set(key, version);
}
async get(key) {
const data = await this.cache.get(key);
if (!data) return null;
// 检查版本是否是最新的
const currentVersion = this.versionMap.get(key);
if (data.version !== currentVersion) {
return null; // 版本过期
}
return data.value;
}
// 标记数据为已更新
invalidate(key) {
this.versionMap.set(key, Date.now());
}
}
最佳实践
1. 缓存设计原则
javascript
// 缓存设计检查清单
const CACHE_CHECKLIST = {
// 1. 明确缓存对象
identifyCacheTargets: () => {
// - 静态资源(CSS、JS、图片)
// - API 数据(用户信息、列表数据)
// - 计算结果(复杂计算、排序、筛选)
// - 页面组件(React/Vue 组件)
},
// 2. 选择合适策略
selectStrategy: () => {
// - 静态资源:Cache First
// - 频繁更新:Network First
// - 展示优先:Stale While Revalidate
// - 敏感数据:Network Only
},
// 3. 设置合理 TTL
setTTL: () => {
// - 用户信息:5-10分钟
// - 静态资源:1年
// - 配置数据:1小时
// - 列表数据:1-5分钟
},
// 4. 缓存失效策略
invalidationStrategy: () => {
// - 基于时间的失效
// - 基于版本的失效
// - 基于事件的失效
// - 手动失效
},
// 5. 监控和统计
monitoring: () => {
// - 命中率统计
// - 缓存大小监控
// - 性能指标
// - 错误率统计
}
};
2. 缓存最佳实践
javascript
// 缓存实践指南
class CacheBestPractices {
// 1. 缓存键命名规范
static generateCacheKey(base, params) {
return `${base}:${JSON.stringify(params)}`;
}
// 2. 缓存数据大小限制
static isCacheableSize(size, maxSize = 1024 * 1024) {
return size <= maxSize;
}
// 3. 压缩大数据
static async compress(data) {
if (typeof data === 'string') {
return LZString.compress(data);
}
return JSON.stringify(data);
}
// 4. 批量操作优化
static async batchGet(cache, keys) {
const promises = keys.map(key => cache.get(key));
return Promise.all(promises);
}
static async batchSet(cache, items) {
const promises = items.map(({ key, value, ttl }) =>
cache.set(key, value, ttl)
);
return Promise.all(promises);
}
// 5. 预加载常用数据
static async preload(cache, configs) {
const promises = configs.map(({ key, fetcher, ttl }) =>
cache.get(key).then(value => {
if (value) return value;
return fetcher().then(data => {
cache.set(key, data, ttl);
return data;
});
})
);
return Promise.all(promises);
}
}
3. 性能优化技巧
javascript
// 性能优化技巧
class CachePerformanceOptimization {
// 1. 异步缓存
static async asyncCache(cache, key, fetcher, ttl) {
const cached = cache.get(key);
if (cached) return cached;
// 启动后台加载
fetcher().then(data => {
cache.set(key, data, ttl);
}).catch(error => {
console.error('Async cache load failed:', error);
});
// 返回占位数据或 null
return null;
}
// 2. 缓存预取
static prefetch(cache, urls) {
urls.forEach(url => {
// 空闲时预取
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
cache.get(url);
});
} else {
setTimeout(() => cache.get(url), 0);
}
});
}
// 3. 缓存分片
static shardCache(cache, shardCount = 10) {
const shardedCaches = Array.from({ length: shardCount }, () => new Map());
return {
get(key) {
const shardIndex = this.getShardIndex(key);
return shardedCaches[shardIndex].get(key);
},
set(key, value, ttl) {
const shardIndex = this.getShardIndex(key);
shardedCaches[shardIndex].set(key, value);
},
getShardIndex(key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash = (hash << 5) - hash + key.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash) % shardCount;
}
};
}
// 4. 缓存统计和优化建议
static analyzeCache(cache) {
const stats = cache.getStats();
const suggestions = [];
if (stats.hitRate < 0.8) {
suggestions.push('命中率低于80%,建议调整缓存策略');
}
if (stats.size > 100) {
suggestions.push('缓存项过多,考虑使用 LRU 策略');
}
return { stats, suggestions };
}
}
4. 安全性考虑
javascript
// 缓存安全
class SecureCache {
constructor(cache) {
this.cache = cache;
}
// 1. 加密敏感数据
async setSecure(key, value, password, ttl = 0) {
const encrypted = await this.encrypt(value, password);
await this.cache.set(key, encrypted, ttl);
}
async getSecure(key, password) {
const encrypted = await this.cache.get(key);
if (!encrypted) return null;
return await this.decrypt(encrypted, password);
}
async encrypt(data, password) {
// 使用 Web Crypto API 加密
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(JSON.stringify(data));
const passwordBuffer = encoder.encode(password);
// 导入密钥
const key = await crypto.subtle.importKey(
'raw',
passwordBuffer,
{ name: 'PBKDF2' },
false,
['deriveKey']
);
// 生成盐
const salt = crypto.getRandomValues(new Uint8Array(16));
// 派生密钥
const derivedKey = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
key,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
// 加密数据
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedBuffer = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
derivedKey,
dataBuffer
);
// 返回加密后的数据(包含盐和 IV)
return {
salt: Array.from(salt),
iv: Array.from(iv),
data: Array.from(new Uint8Array(encryptedBuffer))
};
}
async decrypt(encryptedData, password) {
// 解密实现
const { salt, iv, data } = encryptedData;
// ... 解密逻辑
return JSON.parse(decryptedString);
}
// 2. 防止缓存投毒
validateCacheData(data) {
// 验证数据结构
if (typeof data !== 'object' || data === null) {
return false;
}
// 检查敏感字段
if (data.password || data.token || data.secret) {
console.warn('Sensitive data in cache');
return false;
}
return true;
}
// 3. 访问控制
async getWithAccessCheck(key, accessToken) {
// 验证访问令牌
if (!this.validateAccessToken(accessToken)) {
throw new Error('Access denied');
}
const data = await this.cache.get(key);
if (!this.validateCacheData(data)) {
throw new Error('Invalid cache data');
}
return data;
}
}
总结
缓存技术总结
缓存是前端性能优化的核心技术之一,它通过在多个层级存储数据副本来提高访问速度和减少网络请求。
主要缓存技术
- HTTP 缓存:浏览器层面的缓存,控制静态资源的缓存行为
- Service Worker 缓存:强大的缓存控制能力,支持离线应用
- 内存缓存:最快的缓存方式,适用于临时数据
- 状态管理缓存:应用层面的数据缓存,提升用户体验
- 浏览器存储缓存:本地持久化存储,跨会话数据共享
关键策略
- Cache First:适用于静态资源
- Network First:适用于频繁更新的数据
- Stale While Revalidate:平衡性能和新鲜度
- Cache Only:适用于离线页面
- Network Only:适用于敏感数据
最佳实践
- 合理选择缓存层级:多层缓存架构
- 设置合适的 TTL:根据数据特性调整过期时间
- 监控缓存性能:跟踪命中率和性能指标
- 处理缓存问题:防止穿透、雪崩、击穿
- 保障数据安全:加密敏感数据,验证缓存内容