Service Worker 高级缓存架构与生命周期
缓存命中原理与策略选择
在 Service Worker 的生命周期 中,安装阶段会将核心资源缓存在 CacheStorage,以实现首次访问后的快速命中。理解这一点,有助于在离线模式下提供稳定的页面渲染。通过合理的命中策略,可以在用户离线时继续提供可用内容,同时减少对网络的依赖。
Fetch 事件是缓存策略的核心入口,它决定了浏览器在命中缓存、发起网络请求或两者结合时的行为。通过拦截 <fetch>,可以根据资源类型、请求策略以及版本控制来动态决定返回缓存还是网络响应,从而实现更高的离线可用性。
self.addEventListener('install', event => {event.waitUntil(caches.open('v1').then(cache =&; cache.addAll(['/','/index.html','/styles.css','/app.js','/offline.html'])));
});动态缓存与版本管理
为了实现长期的缓存有效性,必须对 CacheStorage 进行版本管理。使用 版本化的缓存名称,可以在 activate 阶段清理旧版本的缓存,避免缓存膨胀与冲突。
版本控制帮助我们按需更新资源,在用户打开应用时自动完成缓存升级,同时确保离线体验的稳定性。通过清理旧缓存,可以减少磁盘占用并降低读取成本。
self.addEventListener('activate', event => {const currentCaches = ['v1', 'assets-v1'];event.waitUntil(caches.keys().then(cacheNames => Promise.all(cacheNames.filter(name =&; !currentCaches.includes(name)).map(name =&; caches.delete(name)))));
});常用缓存策略与适用场景
Cache First(缓存优先)
资源已经缓存且变化不频繁时,Cache First 策略能提供极致的离线体验和低延迟。适用于框架静态资源、通用图标、字体等不会频繁变更的内容。
在实现时,优先从 CacheStorage读取资源,若未命中再发起网络请求并将获取到的响应写入缓存,以便后续请求可用。
self.addEventListener('fetch', event => {event.respondWith(caches.match(event.request).then(cached => {return cached || fetch(event.request).then(response => {return caches.open('v1').then(cache =&; {cache.put(event.request, response.clone());return response;});});}));
});Network First(网络优先)
当应用需要实时数据、用户经常更新内容时,Network First 是更合适的选择。它优先尝试从网络获取最新数据,网络失败时回退到缓存以保障离线可用性。
网络优先策略能确保页面与数据的新鲜度,同时在离线时仍可使用缓存版本的资源,提升鲁棒性。
self.addEventListener('fetch', event => {event.respondWith(fetch(event.request).then(networkResponse => {return caches.open('v1').then(cache =&; {cache.put(event.request, networkResponse.clone());return networkResponse;});}).catch(() => caches.match(event.request)));
});Stale-While-Revalidate(过时但快速再验证)
该策略在用户可用的同时,后台发起网络请求更新缓存,以实现快速渲染与数据新鲜度之间的平衡。
实现要点是:先返回缓存中的旧版本(快速渲染),随后发起网络请求并用新鲜数据更新缓存,下一次请求就能获得最新内容。
self.addEventListener('fetch', event => {event.respondWith(caches.open('v1').then(cache =&; {return cache.match(event.request).then(cached =&; {const network = fetch(event.request).then(networkResponse =&; {cache.put(event.request, networkResponse.clone());return networkResponse;});return cached || network;});}));
});离线优化与资源分发策略
离线首页与核心资源的预缓存
对用户首屏渲染至关重要的资源,应该在 安装阶段就进行预缓存。这样在离线状态下,应用仍然能够快速呈现并提供核心功能。
通过 预缓存名单,把入口页面、样式、脚本等放入初始缓存集合,确保首次离线访问体验的平滑性。
self.addEventListener('install', event => {event.waitUntil(caches.open('v1').then(cache =&; cache.addAll(['/','/index.html','/styles.css','/app.js','/offline.html'])));
});动态资源缓存与容量管理
对于经常变化的资源,使用动态缓存并结合容量控制,可以在保持离线能力的同时防止缓存膨胀。
容量管理可以通过定期清理策略实现,例如对缓存中的条目数量进行上限控制,删除最早创建的条目以维持缓存健康。
const MAX_ENTRIES = 100;
function trimCache(cache) {cache.keys().then(keys => {if (keys.length > MAX_ENTRIES) {const toDelete = keys.slice(0, keys.length - MAX_ENTRIES);return Promise.all(toDelete.map(request =&; cache.delete(request)));}});
}
self.addEventListener('fetch', event => {event.respondWith(caches.open('v1').then(cache =&; fetch(event.request).then(response =&; {cache.put(event.request, response.clone());trimCache(cache);return response;}).catch(() =&; caches.match(event.request))));
});离线体验的监控与调试
离线测试与浏览器开发工具
在开发阶段,使用浏览器的 Application/Storage 面板、Service Worker 面板和网络请求记录,可以实时查看缓存命中、失效和版本升级情况,从而快速定位问题。
跳过等待阶段与强制激活等方法,能帮助你在调试阶段快速验证新版本的缓存行为与离线体验。
// 让新 Service Worker 在安装后立即生效
self.addEventListener('install', event => {self.skipWaiting();
});性能指标与离线可用性度量
衡量离线优化的有效性时,可以关注离线可用性、首次离线渲染速度以及缓存命中率等指标。结合 Lighthouse 等工具,可以量化 缓存策略与离线优化带来的性能提升。

通过持续的监控与测试,能发现潜在的资源漏 cache、过期策略不当等问题,并据此改进缓存分发与更新策略。


