在现代网页应用中,Service Worker 提供了强大的离线能力和网络请求控制。本篇文章聚焦于 JavaScript 中的 Service Worker 缓存策略全解析,涵盖离线缓存、网络优先,以及如何进行自定义实现。
1. 离线缓存策略概述
离线缓存的原理与目标
在离线场景下,浏览器通过缓存资源来提供可用性。离线缓存的核心目标是让关键资源在没有网络时仍然可用,包括 HTML、CSS、JS、图片等。
通过 Cache API,可以将静态资源预先写入缓存,也可以在运行时把新请求缓存在动态缓存中。合理的分层缓存结构有助于提升离线访问的可靠性与速度。
缓存组件与生命周期
核心组件包括 Service Worker、CacheStorage、Request/Response 对象,以及生命周期阶段:安装(install)、激活(activate)和抓取(fetch)。在安装阶段可以使用 cache.addAll 将重要资源放入缓存,以实现离线访问,随后在激活阶段清理旧版本资源。
离线缓存策略还需要考虑缓存容量与清理策略,避免缓存过度膨胀影响设备存储,并在需要时通过版本控制来逐步替换旧资源。
self.addEventListener('install', function(event) {event.waitUntil(caches.open('offline-v1').then(function(cache) {return cache.addAll(['/','/index.html','/styles.css','/app.js','/logo.png']);}));
});2. 网络优先策略
网络优先工作流程
网络优先策略尝试从网络获取最新资源,只有在网络失败时才回退到缓存。该策略适合需要新鲜数据的应用,如新闻、股票等场景。
通过 fetch 与 caches.match 的组合,可以在网络成功时更新缓存,在网络失败或离线时返回缓存版本,确保用户体验的连贯性。
实现示例与注意点
下面的实现展示如何在 fetch 事件中实现网络优先,并在缓存未命中时回退。注意处理跨域、响应克隆以及缓存并发。
实现要点包括:正确克隆响应、确保缓存更新、对失败场景给出稳定回退。
self.addEventListener('fetch', function(event) {event.respondWith(fetch(event.request).then(function(networkResponse) {// 将网络响应写入动态缓存caches.open('dynamic-v1').then(function(cache) {cache.put(event.request, networkResponse.clone());});return networkResponse;}).catch(function() {// 网络不可用时降级为缓存版本return caches.match(event.request);}));
});3. 自定义实现:混合策略与高级技巧
自定义策略的设计原则
自定义实现旨在结合离线缓存与网络优先,构建可控、可扩展的策略。设计时应关注命中率、缓存容量、过期清理和版本管理,以实现稳定且可维护的缓存行为。

一个清晰的策略分层有助于应对不同路由的需求:静态资源优先缓存、动态请求按需缓存、定期清理与版本迁移,从而降低缓存冲突并提升更新体验。
跨路由缓存和动态缓存
跨路由缓存允许对不同请求路径应用不同的缓存策略,动态缓存用于运行时请求的缓存管理,提升离线体验,并可结合诸如 stale-while-revalidate 等高级缓存模式来平衡速度与新鲜度。
在实现自定义策略时,应考虑缓存击穿、并发控制以及清晰的版本标记,以便后续升级和维护。
// 示例:简单的混合策略 - 静态资源优先,动态请求走网络并缓存
self.addEventListener('fetch', async (event) => {if (event.request.method !== 'GET') return;const cacheStatic = await caches.open('static-v1');const cachedStatic = await cacheStatic.match(event.request);if (cachedStatic) return cachedStatic;try {const networkRes = await fetch(event.request);const cacheDynamic = await caches.open('dynamic-v1');if (networkRes && networkRes.ok) {cacheDynamic.put(event.request, networkRes.clone());}return networkRes;} catch (e) {const cacheDynamic = await caches.open('dynamic-v1');const cachedDynamic = await cacheDynamic.match(event.request);return cachedDynamic || new Response('Offline', { status: 503 });}
});通过上述示例可以看到,自定义实现并非单一策略,而是将多种策略组合成一个适配场景的解决方案,以满足不同资源的缓存需求与离线体验。


