Service Worker 离线缓存原理与实战

为什么需要 Service Worker

在移动互联网时代,用户对 Web 应用的期望已经不再局限于“联网才能使用”。地铁隧道、电梯间、飞机上——网络不稳定甚至完全断开的场景比比皆是。传统的 HTTP 缓存虽然能加速资源加载,但它完全依赖浏览器和服务器之间的协议约定,开发者能做的控制非常有限。而 Service Worker 的出现,让 Web 开发者第一次真正拥有了拦截网络请求、自主决定缓存策略的能力。

Service Worker 本质上是运行在浏览器后台的独立线程,它独立于主线程,不阻塞页面渲染,也不能直接访问 DOM。但正是这个“幕后工作者”,让离线体验、推送通知、后台同步等能力成为可能。本文将从原理出发,逐步深入到实战编码,帮助你彻底掌握 Service Worker 的离线缓存机制。

Service Worker 的核心原理

生命周期

理解 Service Worker 的关键在于理解它的生命周期。一个 Service Worker 从注册到生效,会经历以下几个阶段:

  1. 注册(Register) :主线程通过 navigator.serviceWorker.register() 注册脚本文件,浏览器在后台下载并解析。
  2. 安装(Install) :触发 install 事件,通常在这个阶段预缓存核心静态资源。
  3. 等待(Waiting) :如果已有旧版 Service Worker 在运行,新版会进入等待状态,直到所有相关页面关闭。
  4. 激活(Activate) :触发 activate 事件,通常在此清理旧缓存。
  5. 拦截(Fetch) :激活后,Service Worker 开始接管页面发出的网络请求,触发 fetch 事件。

缓存机制

Service Worker 使用 Cache Storage API 来存储资源。它与浏览器 HTTP 缓存是两套独立体系,互不干扰。Cache Storage 以键值对形式存储 Request/Response 对象,开发者可以精确控制哪些资源被缓存、何时更新、何时删除。

核心 API 包括:

  • caches.open(name):打开或创建一个命名缓存。
  • cache.addAll(urls):批量缓存资源,任一失败则整体失败。
  • cache.match(request):查找匹配的缓存响应。
  • cache.put(request, response):手动写入缓存。
  • caches.delete(name):删除整个缓存。

实战:构建一个离线可用的页面

下面我们通过一个完整的示例,实现一个支持离线访问的页面。

第一步:注册 Service Worker

在主线程脚本中注册:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(reg => console.log('SW 注册成功:', reg.scope))
      .catch(err => console.error('SW 注册失败:', err));
  });
}

注意:Service Worker 只能运行在 HTTPS 或 localhost 环境下,这是安全策略的硬性要求。

第二步:编写 sw.js

const CACHE_NAME = 'my-site-v1';
const PRECACHE_URLS = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/app.js',
  '/images/logo.png'
];

// 安装阶段:预缓存核心资源
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(PRECACHE_URLS))
      .then(() => self.skipWaiting())
  );
});

// 激活阶段:清理旧缓存
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all(
        keys.filter(key => key !== CACHE_NAME)
            .map(key => caches.delete(key))
      );
    }).then(() => self.clients.claim())
  );
});

// 拦截请求:缓存优先策略
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(cached => {
        if (cached) return cached;
        return fetch(event.request).then(response => {
          // 只缓存同源 GET 请求
          if (event.request.method !== 'GET' ||
              !response || response.status !== 200 ||
              response.type !== 'basic') {
            return response;
          }
          const clone = response.clone();
          caches.open(CACHE_NAME).then(cache => {
            cache.put(event.request, clone);
          });
          return response;
        });
      })
  );
});

第三步:理解缓存策略

上面的示例使用的是 Cache First(缓存优先) 策略,适合静态资源。实际项目中,不同资源需要不同策略:

  • Cache First:优先读缓存,适合 CSS、JS、图片等不常变的资源。
  • Network First:优先请求网络,失败时回退缓存,适合 API 数据、HTML 页面。
  • Stale While Revalidate:先返回缓存,同时后台更新,兼顾速度与新鲜度。

以 Network First 为例:

self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request)
      .then(response => {
        const clone = response.clone();
        caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, clone);
        });
        return response;
      })
      .catch(() => caches.match(event.request))
  );
});

常见问题与最佳实践

缓存更新问题:修改 CACHE_NAME 版本号即可触发更新。每次部署新版本时递增版本号,activate 阶段会自动清理旧缓存。

调试技巧:Chrome DevTools 的 Application 面板可以查看已注册的 Service Worker、缓存内容和网络请求走向。勾选 “Offline” 即可模拟离线环境测试。

避免缓存陷阱:不要缓存非 GET 请求、不缓存错误响应(状态码非 200)、谨慎处理跨域资源。对于 API 请求,建议设置合理的过期策略或网络超时回退。

性能建议:预缓存列表不宜过大,只缓存首屏关键资源;其余采用运行时缓存按需填充。同时注意 skipWaiting()clients.claim() 的使用场景,避免新旧版本冲突。

总结

Service Worker 赋予了 Web 应用接近原生 App 的离线能力。它的核心在于拦截请求、自主控制缓存策略。掌握生命周期、Cache Storage API 以及 Cache First / Network First / Stale While Revalidate 三种基本策略,就能应对绝大多数离线场景。实际项目中,建议结合 Workbox 等成熟库来简化缓存管理,但理解底层原理仍然是排查问题和优化体验的基础。从今天开始,为你的网站加上 Service Worker,让用户在地铁里也能流畅访问。

未经允许不得转载:任鹏个人博客 » Service Worker 离线缓存原理与实战

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏