Search K
Appearance
Appearance
📊 SEO元描述:2024年最新Vue缓存策略教程,详解HTTP缓存、Service Worker、浏览器缓存。包含完整缓存方案,适合Vue.js开发者快速掌握Web缓存优化技术。
核心关键词:Vue缓存策略2024、HTTP缓存、Service Worker、浏览器缓存、Vue缓存优化
长尾关键词:Vue缓存策略怎么设计、HTTP缓存最佳实践、Service Worker如何使用、浏览器缓存优化策略、前端缓存性能优化
通过本节Vue缓存策略深度教程,你将系统性掌握:
为什么缓存策略如此重要?这是现代Web性能优化的核心问题。有效的缓存策略能够显著减少网络请求、提升加载速度、改善用户体验,特别是在移动设备和不稳定网络环境下。合理的缓存架构是高性能Vue应用的基础设施。
💡 架构建议:设计多层缓存架构,包括浏览器缓存、应用缓存、CDN缓存和服务端缓存,形成完整的缓存体系
HTTP缓存是Web缓存的基础,需要深入理解其工作原理:
<template>
<div class="http-cache-demo">
<div class="demo-section">
<h3>HTTP缓存机制演示</h3>
<!-- 缓存策略配置 -->
<div class="cache-config">
<h4>缓存策略配置</h4>
<div class="config-grid">
<div class="config-card">
<h5>强缓存 (Cache-Control)</h5>
<div class="config-options">
<label>
<input
type="radio"
v-model="strongCacheStrategy"
value="max-age"
>
max-age (时间缓存)
</label>
<label>
<input
type="radio"
v-model="strongCacheStrategy"
value="immutable"
>
immutable (不可变资源)
</label>
<label>
<input
type="radio"
v-model="strongCacheStrategy"
value="no-cache"
>
no-cache (协商缓存)
</label>
</div>
<div class="config-details">
<div class="detail-item">
<span class="detail-label">缓存时长:</span>
<input
type="range"
min="0"
max="31536000"
v-model="cacheMaxAge"
:disabled="strongCacheStrategy === 'no-cache'"
>
<span class="detail-value">{{ formatCacheTime(cacheMaxAge) }}</span>
</div>
</div>
</div>
<div class="config-card">
<h5>协商缓存 (ETag/Last-Modified)</h5>
<div class="config-options">
<label>
<input
type="checkbox"
v-model="enableETag"
>
启用 ETag
</label>
<label>
<input
type="checkbox"
v-model="enableLastModified"
>
启用 Last-Modified
</label>
</div>
<div class="validation-info">
<div class="info-item">
<span class="info-label">ETag:</span>
<span class="info-value">{{ currentETag }}</span>
</div>
<div class="info-item">
<span class="info-label">Last-Modified:</span>
<span class="info-value">{{ lastModified }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 缓存状态监控 -->
<div class="cache-monitoring">
<h4>缓存状态监控</h4>
<div class="monitoring-stats">
<div class="stat-card">
<div class="stat-title">缓存命中率</div>
<div class="stat-value">{{ cacheHitRate }}%</div>
<div class="stat-trend up">📈</div>
</div>
<div class="stat-card">
<div class="stat-title">节省带宽</div>
<div class="stat-value">{{ savedBandwidth }}MB</div>
<div class="stat-trend up">📈</div>
</div>
<div class="stat-card">
<div class="stat-title">平均响应时间</div>
<div class="stat-value">{{ averageResponseTime }}ms</div>
<div class="stat-trend down">📉</div>
</div>
<div class="stat-card">
<div class="stat-title">缓存大小</div>
<div class="stat-value">{{ cacheSize }}MB</div>
<div class="stat-trend stable">➡️</div>
</div>
</div>
<div class="cache-timeline">
<h5>缓存请求时间线</h5>
<div class="timeline-container">
<div
v-for="request in cacheRequests"
:key="request.id"
class="timeline-item"
:class="request.cacheStatus"
>
<div class="request-time">{{ formatTime(request.timestamp) }}</div>
<div class="request-url">{{ request.url }}</div>
<div class="request-status">{{ request.cacheStatus }}</div>
<div class="request-size">{{ request.size }}KB</div>
<div class="request-duration">{{ request.duration }}ms</div>
</div>
</div>
</div>
</div>
<!-- 缓存测试工具 -->
<div class="cache-testing">
<h4>缓存测试工具</h4>
<div class="testing-controls">
<button @click="testCacheHit">测试缓存命中</button>
<button @click="testCacheMiss">测试缓存未命中</button>
<button @click="testConditionalRequest">测试条件请求</button>
<button @click="clearBrowserCache">清空浏览器缓存</button>
</div>
<div class="test-results">
<div
v-for="result in testResults"
:key="result.id"
class="test-result"
:class="result.type"
>
<div class="result-header">
<span class="result-title">{{ result.title }}</span>
<span class="result-time">{{ formatTime(result.timestamp) }}</span>
</div>
<div class="result-details">
<div class="detail-row">
<span class="detail-label">请求URL:</span>
<span class="detail-value">{{ result.url }}</span>
</div>
<div class="detail-row">
<span class="detail-label">响应状态:</span>
<span class="detail-value">{{ result.status }}</span>
</div>
<div class="detail-row">
<span class="detail-label">缓存状态:</span>
<span class="detail-value">{{ result.cacheStatus }}</span>
</div>
<div class="detail-row">
<span class="detail-label">响应时间:</span>
<span class="detail-value">{{ result.responseTime }}ms</span>
</div>
</div>
</div>
</div>
</div>
<!-- 缓存策略建议 -->
<div class="cache-recommendations">
<h4>缓存策略建议</h4>
<div class="recommendations-list">
<div
v-for="recommendation in cacheRecommendations"
:key="recommendation.id"
class="recommendation-item"
:class="recommendation.priority"
>
<div class="recommendation-header">
<span class="recommendation-title">{{ recommendation.title }}</span>
<span class="recommendation-priority">{{ recommendation.priority }}</span>
</div>
<div class="recommendation-description">
{{ recommendation.description }}
</div>
<div class="recommendation-implementation">
<strong>实现方式:</strong>
<pre><code>{{ recommendation.implementation }}</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'HTTPCacheDemo',
data() {
return {
strongCacheStrategy: 'max-age',
cacheMaxAge: 86400, // 1天
enableETag: true,
enableLastModified: true,
currentETag: '"abc123def456"',
lastModified: new Date().toUTCString(),
// 监控数据
cacheHitRate: 85,
savedBandwidth: 12.5,
averageResponseTime: 120,
cacheSize: 45.2,
cacheRequests: [
{
id: 1,
timestamp: Date.now() - 5000,
url: '/api/users',
cacheStatus: 'hit',
size: 25,
duration: 5
},
{
id: 2,
timestamp: Date.now() - 3000,
url: '/images/avatar.jpg',
cacheStatus: 'miss',
size: 150,
duration: 250
},
{
id: 3,
timestamp: Date.now() - 1000,
url: '/api/posts',
cacheStatus: 'revalidated',
size: 80,
duration: 45
}
],
testResults: [],
cacheRecommendations: [
{
id: 1,
title: '静态资源长期缓存',
priority: 'high',
description: '对于带有版本号的静态资源(JS、CSS、图片),设置长期缓存',
implementation: 'Cache-Control: public, max-age=31536000, immutable'
},
{
id: 2,
title: 'API数据短期缓存',
priority: 'medium',
description: '对于变化频率较低的API数据,设置短期缓存',
implementation: 'Cache-Control: public, max-age=300, must-revalidate'
},
{
id: 3,
title: 'HTML文件协商缓存',
priority: 'high',
description: '对于HTML文件,使用协商缓存确保内容更新',
implementation: 'Cache-Control: no-cache\nETag: "version-hash"'
},
{
id: 4,
title: '用户相关数据禁用缓存',
priority: 'critical',
description: '对于包含用户敏感信息的接口,禁用缓存',
implementation: 'Cache-Control: no-store, no-cache, must-revalidate'
}
]
}
},
mounted() {
this.startCacheMonitoring()
},
methods: {
startCacheMonitoring() {
// 模拟缓存监控
setInterval(() => {
this.updateCacheStats()
}, 5000)
},
updateCacheStats() {
// 模拟缓存统计数据更新
this.cacheHitRate = Math.max(70, Math.min(95, this.cacheHitRate + (Math.random() - 0.5) * 5))
this.savedBandwidth += Math.random() * 0.5
this.averageResponseTime = Math.max(50, Math.min(300, this.averageResponseTime + (Math.random() - 0.5) * 20))
this.cacheSize += Math.random() * 0.1
},
formatCacheTime(seconds) {
if (seconds < 60) return `${seconds}秒`
if (seconds < 3600) return `${Math.round(seconds / 60)}分钟`
if (seconds < 86400) return `${Math.round(seconds / 3600)}小时`
return `${Math.round(seconds / 86400)}天`
},
formatTime(timestamp) {
return new Date(timestamp).toLocaleTimeString()
},
async testCacheHit() {
const result = {
id: Date.now(),
title: '缓存命中测试',
type: 'success',
timestamp: Date.now(),
url: '/api/cached-data',
status: '200 OK',
cacheStatus: 'HIT',
responseTime: 5
}
this.testResults.unshift(result)
this.addCacheRequest('hit', '/api/cached-data', 5)
},
async testCacheMiss() {
const result = {
id: Date.now(),
title: '缓存未命中测试',
type: 'warning',
timestamp: Date.now(),
url: '/api/fresh-data',
status: '200 OK',
cacheStatus: 'MISS',
responseTime: 250
}
this.testResults.unshift(result)
this.addCacheRequest('miss', '/api/fresh-data', 250)
},
async testConditionalRequest() {
const result = {
id: Date.now(),
title: '条件请求测试',
type: 'info',
timestamp: Date.now(),
url: '/api/conditional-data',
status: '304 Not Modified',
cacheStatus: 'REVALIDATED',
responseTime: 45
}
this.testResults.unshift(result)
this.addCacheRequest('revalidated', '/api/conditional-data', 45)
},
clearBrowserCache() {
if ('caches' in window) {
caches.keys().then(names => {
names.forEach(name => {
caches.delete(name)
})
})
}
const result = {
id: Date.now(),
title: '清空浏览器缓存',
type: 'info',
timestamp: Date.now(),
url: 'browser://cache',
status: 'Cleared',
cacheStatus: 'CLEARED',
responseTime: 0
}
this.testResults.unshift(result)
this.cacheRequests = []
},
addCacheRequest(status, url, duration) {
const request = {
id: Date.now(),
timestamp: Date.now(),
url,
cacheStatus: status,
size: Math.round(Math.random() * 100 + 20),
duration
}
this.cacheRequests.unshift(request)
// 保持请求历史在合理范围内
if (this.cacheRequests.length > 10) {
this.cacheRequests.pop()
}
}
}
}
</script>
<style scoped>
.demo-section {
padding: 24px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.cache-config,
.cache-monitoring,
.cache-testing,
.cache-recommendations {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #e9ecef;
border-radius: 8px;
}
.config-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 16px;
}
.config-card {
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #007bff;
}
.config-card h5 {
margin: 0 0 12px 0;
color: #333;
}
.config-options {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
}
.config-options label {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.config-details,
.validation-info {
border-top: 1px solid #ddd;
padding-top: 12px;
}
.detail-item,
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.detail-label,
.info-label {
font-size: 14px;
color: #666;
}
.detail-value,
.info-value {
font-size: 14px;
font-weight: bold;
color: #007bff;
font-family: monospace;
}
.monitoring-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.stat-card {
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
text-align: center;
position: relative;
}
.stat-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #007bff;
margin-bottom: 4px;
}
.stat-trend {
position: absolute;
top: 8px;
right: 8px;
font-size: 16px;
}
.timeline-container {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
}
.timeline-item {
display: grid;
grid-template-columns: 100px 2fr 100px 80px 80px;
gap: 12px;
padding: 12px;
border-bottom: 1px solid #eee;
align-items: center;
font-size: 14px;
}
.timeline-item:hover {
background: #f8f9fa;
}
.timeline-item.hit {
border-left: 4px solid #28a745;
}
.timeline-item.miss {
border-left: 4px solid #dc3545;
}
.timeline-item.revalidated {
border-left: 4px solid #ffc107;
}
.request-time {
font-family: monospace;
color: #666;
}
.request-url {
font-family: monospace;
color: #007bff;
}
.request-status {
font-weight: bold;
text-transform: uppercase;
}
.request-size,
.request-duration {
font-family: monospace;
color: #666;
}
.testing-controls {
display: flex;
gap: 12px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.testing-controls button {
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.testing-controls button:hover {
background: #0056b3;
}
.test-results {
max-height: 400px;
overflow-y: auto;
}
.test-result {
margin-bottom: 16px;
padding: 16px;
border-radius: 8px;
border-left: 4px solid #007bff;
}
.test-result.success {
background: #d4edda;
border-left-color: #28a745;
}
.test-result.warning {
background: #fff3cd;
border-left-color: #ffc107;
}
.test-result.info {
background: #d1ecf1;
border-left-color: #17a2b8;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.result-title {
font-weight: bold;
font-size: 16px;
}
.result-time {
font-size: 12px;
color: #666;
font-family: monospace;
}
.result-details {
display: grid;
gap: 6px;
}
.detail-row {
display: flex;
justify-content: space-between;
font-size: 14px;
}
.detail-row .detail-label {
color: #666;
}
.detail-row .detail-value {
font-family: monospace;
color: #333;
}
.recommendations-list {
display: grid;
gap: 16px;
}
.recommendation-item {
padding: 16px;
border-radius: 8px;
border-left: 4px solid #007bff;
}
.recommendation-item.critical {
background: #fff5f5;
border-left-color: #e53e3e;
}
.recommendation-item.high {
background: #fff3cd;
border-left-color: #ffc107;
}
.recommendation-item.medium {
background: #d1ecf1;
border-left-color: #17a2b8;
}
.recommendation-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.recommendation-title {
font-weight: bold;
font-size: 16px;
}
.recommendation-priority {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.recommendation-item.critical .recommendation-priority {
background: #fed7d7;
color: #c53030;
}
.recommendation-item.high .recommendation-priority {
background: #fef5e7;
color: #d69e2e;
}
.recommendation-item.medium .recommendation-priority {
background: #bee3f8;
color: #2b6cb0;
}
.recommendation-description {
margin-bottom: 12px;
color: #666;
line-height: 1.5;
}
.recommendation-implementation {
font-size: 14px;
}
.recommendation-implementation pre {
margin: 8px 0 0 0;
padding: 8px;
background: #f8f9fa;
border-radius: 4px;
font-size: 12px;
overflow-x: auto;
}
</style>Service Worker是运行在浏览器后台的脚本,提供离线缓存、后台同步等高级功能:
// public/sw.js - Service Worker实现
const CACHE_NAME = 'vue-app-v1.0.0'
const STATIC_CACHE = 'static-v1.0.0'
const DYNAMIC_CACHE = 'dynamic-v1.0.0'
// 需要缓存的静态资源
const STATIC_ASSETS = [
'/',
'/index.html',
'/manifest.json',
'/static/css/app.css',
'/static/js/app.js',
'/static/js/vendor.js',
'/images/logo.png',
'/images/icons/icon-192x192.png'
]
// 需要缓存的API路径
const CACHE_API_PATTERNS = [
/^\/api\/users/,
/^\/api\/posts/,
/^\/api\/config/
]
// 安装事件 - 预缓存静态资源
self.addEventListener('install', event => {
console.log('Service Worker installing...')
event.waitUntil(
caches.open(STATIC_CACHE)
.then(cache => {
console.log('Caching static assets')
return cache.addAll(STATIC_ASSETS)
})
.then(() => {
console.log('Static assets cached')
return self.skipWaiting()
})
)
})
// 激活事件 - 清理旧缓存
self.addEventListener('activate', event => {
console.log('Service Worker activating...')
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
console.log('Deleting old cache:', cacheName)
return caches.delete(cacheName)
}
})
)
})
.then(() => {
console.log('Old caches cleaned up')
return self.clients.claim()
})
)
})
// 拦截网络请求
self.addEventListener('fetch', event => {
const { request } = event
const url = new URL(request.url)
// 处理导航请求
if (request.mode === 'navigate') {
event.respondWith(handleNavigationRequest(request))
return
}
// 处理静态资源
if (isStaticAsset(request)) {
event.respondWith(handleStaticAsset(request))
return
}
// 处理API请求
if (isAPIRequest(request)) {
event.respondWith(handleAPIRequest(request))
return
}
// 处理图片资源
if (isImageRequest(request)) {
event.respondWith(handleImageRequest(request))
return
}
// 默认网络优先策略
event.respondWith(fetch(request))
})
// 处理导航请求 - 缓存优先,网络降级
async function handleNavigationRequest(request) {
try {
// 尝试从网络获取最新版本
const networkResponse = await fetch(request)
if (networkResponse.ok) {
// 更新缓存
const cache = await caches.open(DYNAMIC_CACHE)
cache.put(request, networkResponse.clone())
return networkResponse
}
} catch (error) {
console.log('Network failed, trying cache:', error)
}
// 网络失败,从缓存获取
const cachedResponse = await caches.match(request)
if (cachedResponse) {
return cachedResponse
}
// 返回离线页面
return caches.match('/offline.html')
}
// 处理静态资源 - 缓存优先
async function handleStaticAsset(request) {
const cachedResponse = await caches.match(request)
if (cachedResponse) {
return cachedResponse
}
try {
const networkResponse = await fetch(request)
if (networkResponse.ok) {
const cache = await caches.open(STATIC_CACHE)
cache.put(request, networkResponse.clone())
}
return networkResponse
} catch (error) {
console.log('Failed to fetch static asset:', error)
throw error
}
}
// 处理API请求 - 网络优先,缓存降级
async function handleAPIRequest(request) {
try {
const networkResponse = await fetch(request)
if (networkResponse.ok) {
// 缓存成功的API响应
const cache = await caches.open(DYNAMIC_CACHE)
cache.put(request, networkResponse.clone())
// 发送缓存更新消息
broadcastCacheUpdate(request.url, 'updated')
}
return networkResponse
} catch (error) {
console.log('API request failed, trying cache:', error)
const cachedResponse = await caches.match(request)
if (cachedResponse) {
// 发送离线模式消息
broadcastCacheUpdate(request.url, 'offline')
return cachedResponse
}
throw error
}
}
// 处理图片请求 - 缓存优先,网络降级
async function handleImageRequest(request) {
const cachedResponse = await caches.match(request)
if (cachedResponse) {
return cachedResponse
}
try {
const networkResponse = await fetch(request)
if (networkResponse.ok) {
const cache = await caches.open(DYNAMIC_CACHE)
cache.put(request, networkResponse.clone())
}
return networkResponse
} catch (error) {
// 返回默认图片
return caches.match('/images/placeholder.png')
}
}
// 工具函数
function isStaticAsset(request) {
return STATIC_ASSETS.some(asset => request.url.includes(asset))
}
function isAPIRequest(request) {
return CACHE_API_PATTERNS.some(pattern => pattern.test(request.url))
}
function isImageRequest(request) {
return request.destination === 'image'
}
// 广播缓存更新消息
function broadcastCacheUpdate(url, status) {
self.clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage({
type: 'CACHE_UPDATE',
url,
status,
timestamp: Date.now()
})
})
})
}
// 后台同步
self.addEventListener('sync', event => {
if (event.tag === 'background-sync') {
event.waitUntil(doBackgroundSync())
}
})
async function doBackgroundSync() {
console.log('Performing background sync...')
try {
// 同步离线时的操作
await syncOfflineActions()
// 更新缓存数据
await updateCachedData()
console.log('Background sync completed')
} catch (error) {
console.error('Background sync failed:', error)
}
}
async function syncOfflineActions() {
// 获取离线时存储的操作
const offlineActions = await getOfflineActions()
for (const action of offlineActions) {
try {
await fetch(action.url, action.options)
await removeOfflineAction(action.id)
} catch (error) {
console.error('Failed to sync action:', error)
}
}
}
async function updateCachedData() {
// 更新关键数据的缓存
const criticalAPIs = [
'/api/user/profile',
'/api/app/config',
'/api/notifications'
]
for (const api of criticalAPIs) {
try {
const response = await fetch(api)
if (response.ok) {
const cache = await caches.open(DYNAMIC_CACHE)
cache.put(api, response)
}
} catch (error) {
console.error('Failed to update cached data:', error)
}
}
}
// IndexedDB操作(简化版)
async function getOfflineActions() {
// 实际项目中使用IndexedDB存储离线操作
return []
}
async function removeOfflineAction(id) {
// 从IndexedDB中移除已同步的操作
}Service Worker核心功能:
💼 Service Worker提示:注意Service Worker的生命周期管理,合理设计缓存更新策略,避免缓存过期和版本冲突问题
设计完整的Vue应用缓存管理系统:
// utils/cacheManager.js - 应用级缓存管理器
class VueCacheManager {
constructor(options = {}) {
this.options = {
enableMemoryCache: true,
enableLocalStorage: true,
enableSessionStorage: true,
enableIndexedDB: true,
defaultTTL: 5 * 60 * 1000, // 5分钟
maxMemorySize: 50 * 1024 * 1024, // 50MB
compressionThreshold: 1024, // 1KB
...options
}
this.memoryCache = new Map()
this.cacheStats = {
hits: 0,
misses: 0,
sets: 0,
deletes: 0,
memoryUsage: 0
}
this.init()
}
init() {
// 初始化IndexedDB
if (this.options.enableIndexedDB) {
this.initIndexedDB()
}
// 定期清理过期缓存
setInterval(() => {
this.cleanExpiredCache()
}, 60000) // 每分钟清理一次
// 监听内存压力
this.setupMemoryPressureHandling()
}
async initIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('VueCacheDB', 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('cache')) {
const store = db.createObjectStore('cache', { keyPath: 'key' })
store.createIndex('expiry', 'expiry', { unique: false })
}
}
})
}
// 设置缓存
async set(key, value, options = {}) {
const config = {
ttl: options.ttl || this.options.defaultTTL,
storage: options.storage || 'auto',
compress: options.compress !== false,
...options
}
const cacheEntry = {
key,
value,
timestamp: Date.now(),
expiry: Date.now() + config.ttl,
size: this.calculateSize(value),
compressed: false
}
// 压缩大数据
if (config.compress && cacheEntry.size > this.options.compressionThreshold) {
cacheEntry.value = await this.compress(value)
cacheEntry.compressed = true
}
// 选择存储方式
const storageType = this.selectStorage(cacheEntry, config.storage)
try {
await this.storeInStorage(storageType, key, cacheEntry)
this.cacheStats.sets++
console.log(`Cache set: ${key} in ${storageType}`)
return true
} catch (error) {
console.error('Cache set failed:', error)
return false
}
}
// 获取缓存
async get(key, options = {}) {
const storageTypes = this.getStorageOrder()
for (const storageType of storageTypes) {
try {
const cacheEntry = await this.getFromStorage(storageType, key)
if (cacheEntry) {
// 检查是否过期
if (Date.now() > cacheEntry.expiry) {
await this.delete(key)
continue
}
// 解压缩
let value = cacheEntry.value
if (cacheEntry.compressed) {
value = await this.decompress(value)
}
// 更新访问时间(仅内存缓存)
if (storageType === 'memory') {
cacheEntry.lastAccess = Date.now()
}
this.cacheStats.hits++
console.log(`Cache hit: ${key} from ${storageType}`)
return value
}
} catch (error) {
console.error(`Cache get error from ${storageType}:`, error)
}
}
this.cacheStats.misses++
console.log(`Cache miss: ${key}`)
return null
}
// 删除缓存
async delete(key) {
const storageTypes = ['memory', 'sessionStorage', 'localStorage', 'indexedDB']
let deleted = false
for (const storageType of storageTypes) {
try {
const result = await this.deleteFromStorage(storageType, key)
if (result) deleted = true
} catch (error) {
console.error(`Cache delete error from ${storageType}:`, error)
}
}
if (deleted) {
this.cacheStats.deletes++
console.log(`Cache deleted: ${key}`)
}
return deleted
}
// 清空所有缓存
async clear() {
const storageTypes = ['memory', 'sessionStorage', 'localStorage', 'indexedDB']
for (const storageType of storageTypes) {
try {
await this.clearStorage(storageType)
} catch (error) {
console.error(`Cache clear error for ${storageType}:`, error)
}
}
console.log('All caches cleared')
}
// 存储选择逻辑
selectStorage(cacheEntry, preferredStorage) {
if (preferredStorage !== 'auto') {
return preferredStorage
}
// 根据数据大小和TTL选择存储
if (cacheEntry.size < 1024) {
return 'memory' // 小数据优先内存
} else if (cacheEntry.expiry - Date.now() < 60000) {
return 'sessionStorage' // 短期数据用sessionStorage
} else if (cacheEntry.size < 1024 * 1024) {
return 'localStorage' // 中等数据用localStorage
} else {
return 'indexedDB' // 大数据用IndexedDB
}
}
getStorageOrder() {
return ['memory', 'sessionStorage', 'localStorage', 'indexedDB']
}
// 存储操作
async storeInStorage(storageType, key, cacheEntry) {
switch (storageType) {
case 'memory':
this.memoryCache.set(key, cacheEntry)
this.updateMemoryUsage()
break
case 'sessionStorage':
if (this.options.enableSessionStorage) {
sessionStorage.setItem(`cache_${key}`, JSON.stringify(cacheEntry))
}
break
case 'localStorage':
if (this.options.enableLocalStorage) {
localStorage.setItem(`cache_${key}`, JSON.stringify(cacheEntry))
}
break
case 'indexedDB':
if (this.options.enableIndexedDB && this.db) {
const transaction = this.db.transaction(['cache'], 'readwrite')
const store = transaction.objectStore('cache')
await store.put(cacheEntry)
}
break
}
}
async getFromStorage(storageType, key) {
switch (storageType) {
case 'memory':
return this.memoryCache.get(key)
case 'sessionStorage':
if (this.options.enableSessionStorage) {
const data = sessionStorage.getItem(`cache_${key}`)
return data ? JSON.parse(data) : null
}
break
case 'localStorage':
if (this.options.enableLocalStorage) {
const data = localStorage.getItem(`cache_${key}`)
return data ? JSON.parse(data) : null
}
break
case 'indexedDB':
if (this.options.enableIndexedDB && this.db) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['cache'], 'readonly')
const store = transaction.objectStore('cache')
const request = store.get(key)
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}
break
}
return null
}
async deleteFromStorage(storageType, key) {
switch (storageType) {
case 'memory':
const deleted = this.memoryCache.delete(key)
if (deleted) this.updateMemoryUsage()
return deleted
case 'sessionStorage':
if (this.options.enableSessionStorage) {
sessionStorage.removeItem(`cache_${key}`)
return true
}
break
case 'localStorage':
if (this.options.enableLocalStorage) {
localStorage.removeItem(`cache_${key}`)
return true
}
break
case 'indexedDB':
if (this.options.enableIndexedDB && this.db) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['cache'], 'readwrite')
const store = transaction.objectStore('cache')
const request = store.delete(key)
request.onsuccess = () => resolve(true)
request.onerror = () => reject(request.error)
})
}
break
}
return false
}
async clearStorage(storageType) {
switch (storageType) {
case 'memory':
this.memoryCache.clear()
this.updateMemoryUsage()
break
case 'sessionStorage':
if (this.options.enableSessionStorage) {
Object.keys(sessionStorage).forEach(key => {
if (key.startsWith('cache_')) {
sessionStorage.removeItem(key)
}
})
}
break
case 'localStorage':
if (this.options.enableLocalStorage) {
Object.keys(localStorage).forEach(key => {
if (key.startsWith('cache_')) {
localStorage.removeItem(key)
}
})
}
break
case 'indexedDB':
if (this.options.enableIndexedDB && this.db) {
const transaction = this.db.transaction(['cache'], 'readwrite')
const store = transaction.objectStore('cache')
await store.clear()
}
break
}
}
// 工具方法
calculateSize(value) {
return JSON.stringify(value).length
}
async compress(value) {
// 简化的压缩实现,实际项目可使用LZ-string等库
return JSON.stringify(value)
}
async decompress(value) {
return JSON.parse(value)
}
updateMemoryUsage() {
let totalSize = 0
for (const [key, entry] of this.memoryCache) {
totalSize += entry.size
}
this.cacheStats.memoryUsage = totalSize
// 内存压力处理
if (totalSize > this.options.maxMemorySize) {
this.evictLRUMemoryCache()
}
}
evictLRUMemoryCache() {
const entries = Array.from(this.memoryCache.entries())
entries.sort((a, b) => (a[1].lastAccess || 0) - (b[1].lastAccess || 0))
// 删除最少使用的25%
const toDelete = Math.ceil(entries.length * 0.25)
for (let i = 0; i < toDelete; i++) {
this.memoryCache.delete(entries[i][0])
}
this.updateMemoryUsage()
console.log(`Evicted ${toDelete} items from memory cache`)
}
cleanExpiredCache() {
const now = Date.now()
// 清理内存缓存
for (const [key, entry] of this.memoryCache) {
if (now > entry.expiry) {
this.memoryCache.delete(key)
}
}
this.updateMemoryUsage()
}
setupMemoryPressureHandling() {
// 监听内存压力事件(如果支持)
if ('memory' in performance && 'addEventListener' in performance.memory) {
performance.memory.addEventListener('memorypressure', () => {
console.log('Memory pressure detected, clearing cache')
this.evictLRUMemoryCache()
})
}
}
// 获取缓存统计
getStats() {
const hitRate = this.cacheStats.hits / (this.cacheStats.hits + this.cacheStats.misses) * 100
return {
...this.cacheStats,
hitRate: isNaN(hitRate) ? 0 : hitRate.toFixed(2),
memoryCacheSize: this.memoryCache.size,
memoryUsageMB: (this.cacheStats.memoryUsage / 1024 / 1024).toFixed(2)
}
}
// 预热缓存
async warmup(data) {
console.log('Warming up cache...')
for (const [key, value] of Object.entries(data)) {
await this.set(key, value, { ttl: 30 * 60 * 1000 }) // 30分钟
}
console.log(`Cache warmed up with ${Object.keys(data).length} items`)
}
}
// Vue插件形式
const VueCachePlugin = {
install(app, options = {}) {
const cacheManager = new VueCacheManager(options)
app.config.globalProperties.$cache = cacheManager
app.provide('cache', cacheManager)
// 全局混入
app.mixin({
methods: {
async $cacheGet(key) {
return await cacheManager.get(key)
},
async $cacheSet(key, value, options) {
return await cacheManager.set(key, value, options)
},
async $cacheDelete(key) {
return await cacheManager.delete(key)
}
}
})
}
}
export { VueCacheManager, VueCachePlugin }
export default VueCacheManager// utils/cacheInvalidation.js - 缓存失效管理
class CacheInvalidationManager {
constructor(cacheManager) {
this.cacheManager = cacheManager
this.dependencies = new Map() // 缓存依赖关系
this.tags = new Map() // 缓存标签
this.versions = new Map() // 版本控制
}
// 设置带依赖的缓存
async setWithDependencies(key, value, dependencies = [], options = {}) {
// 设置缓存
await this.cacheManager.set(key, value, options)
// 记录依赖关系
this.dependencies.set(key, dependencies)
// 为每个依赖添加反向引用
dependencies.forEach(dep => {
if (!this.dependencies.has(dep)) {
this.dependencies.set(dep, [])
}
const dependents = this.dependencies.get(dep)
if (!dependents.includes(key)) {
dependents.push(key)
}
})
}
// 设置带标签的缓存
async setWithTags(key, value, tags = [], options = {}) {
await this.cacheManager.set(key, value, options)
// 记录标签关系
tags.forEach(tag => {
if (!this.tags.has(tag)) {
this.tags.set(tag, new Set())
}
this.tags.get(tag).add(key)
})
}
// 设置版本化缓存
async setWithVersion(key, value, version, options = {}) {
const versionedKey = `${key}:v${version}`
await this.cacheManager.set(versionedKey, value, options)
// 记录版本信息
this.versions.set(key, version)
// 清理旧版本
await this.cleanOldVersions(key, version)
}
// 根据依赖失效缓存
async invalidateByDependency(dependency) {
const dependents = this.dependencies.get(dependency) || []
for (const dependent of dependents) {
await this.cacheManager.delete(dependent)
console.log(`Invalidated cache: ${dependent} (dependency: ${dependency})`)
}
// 清理依赖关系
this.dependencies.delete(dependency)
}
// 根据标签失效缓存
async invalidateByTag(tag) {
const keys = this.tags.get(tag) || new Set()
for (const key of keys) {
await this.cacheManager.delete(key)
console.log(`Invalidated cache: ${key} (tag: ${tag})`)
}
// 清理标签关系
this.tags.delete(tag)
}
// 版本升级失效
async invalidateByVersion(key, newVersion) {
const currentVersion = this.versions.get(key)
if (currentVersion && currentVersion < newVersion) {
// 删除旧版本缓存
const oldKey = `${key}:v${currentVersion}`
await this.cacheManager.delete(oldKey)
// 更新版本
this.versions.set(key, newVersion)
console.log(`Invalidated cache: ${oldKey} (version upgrade: ${currentVersion} -> ${newVersion})`)
}
}
// 清理旧版本
async cleanOldVersions(key, currentVersion) {
const maxVersionsToKeep = 3
// 查找所有版本
const allVersions = []
for (let v = 1; v <= currentVersion; v++) {
const versionedKey = `${key}:v${v}`
if (await this.cacheManager.get(versionedKey)) {
allVersions.push(v)
}
}
// 保留最新的几个版本
if (allVersions.length > maxVersionsToKeep) {
const versionsToDelete = allVersions.slice(0, -maxVersionsToKeep)
for (const version of versionsToDelete) {
const versionedKey = `${key}:v${version}`
await this.cacheManager.delete(versionedKey)
console.log(`Cleaned old version: ${versionedKey}`)
}
}
}
// 智能失效策略
async smartInvalidate(pattern) {
const strategies = {
// 用户相关数据失效
user: async (userId) => {
await this.invalidateByTag(`user:${userId}`)
await this.invalidateByDependency(`user:${userId}`)
},
// 内容更新失效
content: async (contentId) => {
await this.invalidateByTag(`content:${contentId}`)
await this.invalidateByDependency(`content:${contentId}`)
},
// 全局配置失效
config: async () => {
await this.invalidateByTag('config')
await this.invalidateByTag('global')
},
// 时间敏感数据失效
time: async (timeWindow) => {
const now = Date.now()
const cutoff = now - timeWindow
// 失效超过时间窗口的缓存
// 这里需要遍历所有缓存项检查时间戳
console.log(`Invalidating time-sensitive cache older than ${timeWindow}ms`)
}
}
const [type, ...params] = pattern.split(':')
const strategy = strategies[type]
if (strategy) {
await strategy(...params)
} else {
console.warn(`Unknown invalidation pattern: ${pattern}`)
}
}
// 批量失效
async batchInvalidate(operations) {
const results = []
for (const operation of operations) {
try {
switch (operation.type) {
case 'key':
await this.cacheManager.delete(operation.key)
break
case 'tag':
await this.invalidateByTag(operation.tag)
break
case 'dependency':
await this.invalidateByDependency(operation.dependency)
break
case 'pattern':
await this.smartInvalidate(operation.pattern)
break
}
results.push({ ...operation, success: true })
} catch (error) {
results.push({ ...operation, success: false, error: error.message })
}
}
return results
}
// 获取失效统计
getInvalidationStats() {
return {
dependencyCount: this.dependencies.size,
tagCount: this.tags.size,
versionCount: this.versions.size,
totalRelationships: Array.from(this.dependencies.values()).reduce((sum, deps) => sum + deps.length, 0)
}
}
}
export default CacheInvalidationManager通过本节Vue缓存策略深度教程的学习,你已经掌握:
A: 根据数据特性选择:静态资源用强缓存,动态数据用协商缓存,用户敏感数据禁用缓存。考虑更新频率、数据大小和安全性。
A: 首次访问需要下载和安装Service Worker,会有轻微影响。但后续访问会显著提升性能,建议在关键资源加载完成后再注册。
A: 设计合理的缓存失效策略,包括TTL过期、手动失效、版本控制等。建议实现缓存预热和后台更新机制。
A: 使用版本控制、ETag验证、依赖失效等机制。对于强一致性要求的数据,考虑使用较短的TTL或实时验证。
A: 考虑存储空间限制、网络不稳定、电池消耗等因素。建议使用更激进的缓存策略,优先缓存关键数据。
// vue.config.js - 生产环境缓存配置
module.exports = {
pwa: {
workboxOptions: {
cacheId: 'vue-app',
skipWaiting: true,
clientsClaim: true,
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\//,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 5 * 60 // 5分钟
},
cacheableResponse: {
statuses: [0, 200]
}
}
},
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/,
handler: 'CacheFirst',
options: {
cacheName: 'image-cache',
expiration: {
maxEntries: 200,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30天
}
}
}
]
}
}
}"缓存策略是现代Web应用性能优化的核心技术。通过合理的HTTP缓存配置、Service Worker离线能力和应用级缓存管理,我们能够显著提升用户体验,特别是在网络不稳定的环境下。记住,缓存不仅仅是性能优化工具,更是提升应用可靠性和用户体验的重要手段!"