选择缓存策略
发布于 更新于
一句话: 正确的缓存策略取决于资源是静态的、动态的还是时间敏感的——以及用户对过时内容的容忍程度。
Service Worker 拦截 fetch 事件并决定从哪里获取响应:缓存、网络或两者兼有。每种策略在速度、新鲜度和离线可用性之间做出不同的权衡。
先检查缓存;如果没有缓存内容则回退到网络。
async function cacheFirst(request) { const cached = await caches.match(request); if (cached) return cached; return fetch(request);}适用场景: 静态资源(JS 包、CSS、图片、字体),在部署之间不会改变。一旦缓存,响应是即时的——即使离线。
先尝试网络;如果请求失败或超时则回退到缓存。
async function networkFirst(request) { try { const response = await fetch(request); const cache = await caches.open('dynamic'); cache.put(request, response.clone()); return response; } catch { return caches.match(request); }}适用场景: 动态内容(API 响应、用户数据),新鲜度很重要但仍需要离线回退。
陈旧-while-重新验证
Section titled “陈旧-while-重新验证”立即返回缓存的响应,然后在后台获取新副本以供下次使用。
async function staleWhileRevalidate(request) { const cached = await caches.match(request); const fetchPromise = fetch(request).then((response) => { const cache = caches.open('dynamic'); cache.then((c) => c.put(request, response.clone())); return response; }); return cached || fetchPromise;}适用场景: 半静态内容(UI 框架、文章列表),快速显示比显示最新版本更重要。
始终使用网络,不使用缓存。
适用场景: 非 GET 请求(POST、PUT、DELETE)、实时数据(WebSocket 连接),或绝不能过时的资源。
仅从缓存提供服务;如果资源未缓存则失败。
适用场景: 在安装事件中预缓存的资源(应用外壳、离线回退页面)。
| 资源类型 | 推荐策略 | 原因 |
|---|---|---|
| 应用外壳(HTML、CSS、JS) | 缓存优先 | 很少改变;首次访问后即时加载 |
| 图片和字体 | 缓存优先 | 使用哈希文件名时在部署之间不可变 |
| API 数据(用户特定) | 网络优先 | 必须新鲜;缓存提供离线回退 |
| API 数据(共享/半静态) | 陈旧-while-重新验证 | 快速显示缓存数据,后台更新 |
| 导航请求 | 网络优先 + 离线回退 | 在线时获取新鲜 HTML;离线时使用缓存外壳 |
基于时间的过期
Section titled “基于时间的过期”对于既不是不可变的也不是关键的资源,在陈旧-while-重新验证或缓存优先中添加时间检查。将响应时间戳与缓存响应一起存储,如果缓存超过阈值则回退到网络:
async function cacheWithExpiry(request, maxAgeSeconds) { const cached = await caches.match(request); if (cached) { const cachedDate = new Date(cached.headers.get('date')); if (Date.now() - cachedDate.getTime() < maxAgeSeconds * 1000) { return cached; } } return fetch(request);}实用检查清单
Section titled “实用检查清单”- 对带哈希的静态资源(JS、CSS、图片)使用缓存优先。
- 对 API 数据和用户特定内容使用网络优先。
- 对半静态列表和 UI 框架使用陈旧-while-重新验证。
- 在
install事件中为应用外壳添加预缓存步骤。 - 设置包含版本字符串的缓存名称,以便部署时清除旧缓存。
- 限制缓存大小和过期时间以防止无限存储增长。
- 始终为导航请求提供离线回退页面。