缓存策略与离线资源管理
缓存策略的基本概念
缓存策略是Web应用性能优化的核心手段之一。HTML5提供了多种缓存机制,主要包括Service Worker、Cache API、Application Cache(已废弃)等。合理的缓存策略能显著提升页面加载速度,降低服务器负载,并在离线状态下提供基本功能。
浏览器缓存通常分为以下几类:
- 强缓存:通过Expires或Cache-Control头实现
- 协商缓存:通过Last-Modified/If-Modified-Since或ETag/If-None-Match实现
- 应用缓存:通过Service Worker和Cache API实现
// 检查浏览器是否支持Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('ServiceWorker注册成功:', registration.scope);
})
.catch(err => {
console.log('ServiceWorker注册失败:', err);
});
}
Service Worker缓存机制
Service Worker是运行在浏览器后台的脚本,独立于网页线程,可以拦截和处理网络请求。它构成了Progressive Web Apps(PWA)的技术基础。
典型的Service Worker生命周期包括:
- 注册(Register)
- 安装(Install)
- 激活(Activate)
- 闲置(Idle)
- 终止(Terminated)
- 重新激活(Reactivated)
// sw.js示例
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
self.addEventListener('install', event => {
// 执行安装步骤
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('已打开缓存');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中 - 返回响应
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
Cache API详解
Cache API提供了存储网络请求和响应对的机制,是Service Worker缓存功能的基础。它允许开发者创建多个命名缓存,并对其进行增删改查操作。
主要方法包括:
CacheStorage.open()
: 打开指定名称的缓存Cache.add()
: 获取URL资源并添加到缓存Cache.addAll()
: 获取多个URL资源并添加到缓存Cache.put()
: 存储请求/响应对Cache.match()
: 查找缓存中的第一个匹配项Cache.delete()
: 删除缓存项
// 使用Cache API的示例
caches.open('my-cache').then(cache => {
// 添加单个资源
cache.add('/images/photo.jpg');
// 添加多个资源
cache.addAll([
'/styles/style.css',
'/scripts/app.js'
]);
// 自定义请求存储
const response = new Response('Hello world!');
cache.put('/greeting', response);
// 检索缓存
cache.match('/images/photo.jpg').then(response => {
if (response) {
// 使用缓存的响应
}
});
});
离线资源管理策略
有效的离线资源管理需要考虑资源类型、更新频率和存储限制等因素。常见的策略包括:
- 缓存优先策略:优先从缓存加载,失败再请求网络
- 网络优先策略:优先请求网络,失败再使用缓存
- 仅缓存策略:只从缓存加载
- 仅网络策略:只从网络加载
- 缓存后更新策略:快速显示缓存内容,后台更新
// 缓存优先策略实现
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中
if (response) {
return response;
}
// 没有缓存,请求网络
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;
}
);
})
);
});
缓存版本控制与更新
缓存资源需要版本控制以确保用户获取最新内容。常见做法包括:
- 在缓存名称中包含版本号
- 使用内容哈希作为文件名
- 在Service Worker激活阶段清理旧缓存
// 缓存版本控制示例
const CACHE_VERSION = 2;
const CURRENT_CACHES = {
static: `static-cache-v${CACHE_VERSION}`,
dynamic: `dynamic-cache-v${CACHE_VERSION}`
};
self.addEventListener('activate', event => {
// 删除旧版本缓存
const expectedCacheNames = Object.values(CURRENT_CACHES);
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (!expectedCacheNames.includes(cacheName)) {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
存储配额与清理策略
浏览器对缓存存储有限制,需要合理管理存储空间:
- 检查可用配额
- 设置合理的缓存过期时间
- 实现LRU(最近最少使用)算法清理旧缓存
// 检查存储配额
navigator.storage.estimate().then(estimate => {
console.log(`已使用: ${estimate.usage} bytes`);
console.log(`总配额: ${estimate.quota} bytes`);
console.log(`使用比例: ${(estimate.usage / estimate.quota * 100).toFixed(2)}%`);
});
// LRU缓存清理实现
const MAX_ITEMS = 50;
caches.open('my-cache').then(cache => {
cache.keys().then(keys => {
if (keys.length > MAX_ITEMS) {
cache.delete(keys[0]).then(() => {
console.log('清理最旧缓存项');
});
}
});
});
高级缓存模式
对于复杂应用,可能需要组合多种缓存策略:
- 运行时缓存:动态缓存API响应
- 预缓存:提前缓存关键资源
- 回退缓存:为失败请求提供备用内容
- 增量缓存:只缓存变化的部分
// 运行时缓存API响应示例
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(
caches.open('api-cache').then(cache => {
return fetch(event.request).then(response => {
// 只缓存成功的API响应
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
}).catch(() => {
// 网络失败时返回缓存
return cache.match(event.request);
});
})
);
}
});
// 回退缓存示例
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
.catch(() => caches.match('/offline.html'))
);
});
性能优化实践
结合缓存策略提升性能的具体方法:
- 关键资源预加载:使用
<link rel="preload">
提前加载 - 懒加载非关键资源:按需加载图片等大文件
- 数据预取:预测用户行为提前获取数据
- 缓存分区:按资源类型使用不同缓存
<!-- 预加载关键资源 -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="app.js" as="script">
<!-- 懒加载图片 -->
<img data-src="image.jpg" class="lazyload" alt="">
<script>
document.addEventListener("DOMContentLoaded", function() {
const lazyImages = [].slice.call(document.querySelectorAll("img.lazyload"));
if ("IntersectionObserver" in window) {
const lazyImageObserver = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
const lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove("lazyload");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
}
});
</script>
调试与监控
有效的调试和监控手段:
- Chrome开发者工具的Application面板
- 使用navigator.storage API监控存储使用情况
- 实现缓存命中率统计
- 错误日志记录
// 缓存命中率统计
let cacheHits = 0;
let totalRequests = 0;
self.addEventListener('fetch', event => {
totalRequests++;
event.respondWith(
caches.match(event.request).then(response => {
if (response) {
cacheHits++;
console.log(`缓存命中率: ${(cacheHits/totalRequests*100).toFixed(1)}%`);
return response;
}
return fetch(event.request);
})
);
});
// 记录错误
self.addEventListener('error', event => {
console.error('Service Worker错误:', event.message);
// 发送错误日志到服务器
fetch('/log-error', {
method: 'POST',
body: JSON.stringify({
error: event.message,
stack: event.error.stack
})
});
});
安全考虑
缓存实现需要注意的安全问题:
- 避免缓存敏感数据
- 正确处理HTTPS和CORS
- 防范缓存投毒攻击
- 实施适当的缓存验证
// 安全缓存示例
self.addEventListener('fetch', event => {
// 不缓存API请求和敏感URL
if (event.request.url.includes('/api/') ||
event.request.url.includes('/admin/')) {
return fetch(event.request);
}
// 只缓存同源请求
if (new URL(event.request.url).origin !== location.origin) {
return fetch(event.request);
}
// 正常缓存处理
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
实际应用案例
电商网站缓存实现示例:
// 电商SW实现
const CACHE_VERSION = 'ecommerce-v3';
const PRECACHE = [
'/',
'/index.html',
'/styles/main.min.css',
'/scripts/app.min.js',
'/images/logo.svg',
'/offline.html',
'/products/cached.json'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_VERSION)
.then(cache => cache.addAll(PRECACHE))
.then(() => self.skipWaiting())
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_VERSION) {
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
self.addEventListener('fetch', event => {
const requestUrl = new URL(event.request.url);
// API请求使用网络优先策略
if (requestUrl.pathname.startsWith('/api/')) {
event.respondWith(
fetch(event.request)
.then(response => {
// 只缓存GET请求且成功的响应
if (event.request.method === 'GET' && response.status === 200) {
const clone = response.clone();
caches.open(`${CACHE_VERSION}-dynamic`)
.then(cache => cache.put(event.request, clone));
}
return response;
})
.catch(() => {
// 网络失败时尝试返回缓存
return caches.match(event.request);
})
);
return;
}
// 静态资源使用缓存优先策略
event.respondWith(
caches.match(event.request)
.then(response => {
// 如果找到缓存,返回缓存
if (response) {
// 后台更新缓存
fetchAndCache(event.request);
return response;
}
// 否则请求网络
return fetchAndCache(event.request)
.catch(() => {
// 如果也失败,返回离线页面
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
});
})
);
});
function fetchAndCache(request) {
return fetch(request).then(response => {
// 检查响应是否有效
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应
const responseToCache = response.clone();
caches.open(CACHE_VERSION)
.then(cache => {
cache.put(request, responseToCache);
});
return response;
});
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:数据同步与冲突解决