Service Worker 是什么

Service Worker 是浏览器里独立于页面的后台脚本,可以拦截页面发出的网络请求,决定返回缓存还是走网络。它让前端第一次有能力「自己控制网络」,这也是 PWA(渐进式 Web 应用)的核心底座。

它能做到三件事:离线可用(断网也能打开上次访问过的页面)、秒开体验(资源命中缓存免下载)、可控更新(缓存策略自己定,新版本可控发布)。对内容型站点,离线缓存的意义尤其大。

限制要清楚:Service Worker 只能跑在 HTTPS 或 localhost 下;不能直接访问 DOM;生命周期由浏览器管理,随时可能被杀;更新不是即时的。这些限制决定了它的实现方式——事件驱动、无状态、可重启。

很多人把 Service Worker 当成「缓存插件」,其实它是可编程的网络代理。缓存只是它最常用的能力,配合消息推送、后台同步,它能做的事情更多。

Service Worker 是前端拿到的一张「网络代理票」。
本文核心知识点
  • 生命周期install、activate、fetch 三事件。
  • 预缓存安装时缓存静态资源。
  • 运行时缓存请求拦截按策略缓存。
  • Workbox官方工具箱简化开发。
Service Worker 缓存策略配图:蓝调渐变背景上分层缓存架与服务器堆叠的扁平科技插画
资源进缓存层,离线也能秒开。

注册与生命周期

页面里注册 sw.js,浏览器下载脚本后依次进入三个生命周期事件:install(首次安装)、activate(激活)、fetch(拦截请求)。install 阶段适合做预缓存,activate 阶段清理旧缓存。

// 页面注册
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));
  });
}
// sw.js 生命周期
self.addEventListener("install", (event) => {
  event.waitUntil(caches.open("v1").then(c => c.addAll(["/", "/index.html"])));
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.filter(k => k !== "v1").map(k => caches.delete(k))
    ))
  );
  self.clients.claim();
});

waitUntil 告诉浏览器「等这个异步操作完成再继续生命周期」。skipWaiting 和 clients.claim 让新版本尽快接管,否则要等所有页面关闭才生效。

预缓存策略

预缓存(Precache)在安装时把静态资源全部放进 Cache Storage,适合 JS、CSS、图片、字体这些不变的构建产物。配合 contenthash 文件名,预缓存清单每次构建自动更新。

// 预缓存清单(一般由构建工具生成)
const PRECACHE = [
  "/",
  "/index.html",
  "/assets/app-a1b2c3.js",
  "/assets/style-d4e5f6.css",
  "/images/logo.png"
];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("precache-v2")
      .then(c => c.addAll(PRECACHE))
  );
});

注意 addAll 只要有一个资源失败,整个预缓存就失败。生产上建议用 Workbox 的 precacheAndRoute,它会生成 manifest 并处理失败重试。

运行时缓存策略

拦截 fetch 事件,按请求类型选择缓存策略。常用的四种:Cache First(缓存优先,适合图片)、Network First(网络优先,网络失败回缓存,适合页面)、Stale While Revalidate(先回缓存再后台更新,适合 HTML)、Network Only(纯网络,适合支付接口)。

self.addEventListener("fetch", (event) => {
  const url = new URL(event.request.url);
  if (url.origin !== location.origin) return;  // 跨域不管

  // 图片走缓存优先
  if (event.request.destination === "image") {
    event.respondWith(cacheFirst(event.request));
  }
  // 页面走网络优先
  else if (event.request.mode === "navigate") {
    event.respondWith(networkFirst(event.request));
  }
});

async function cacheFirst(request) {
  const cached = await caches.match(request);
  return cached || fetch(request).then(res => {
    const copy = res.clone();
    caches.open("runtime").then(c => c.put(request, copy));
    return res;
  });
}

缓存策略的选择依据:数据多新鲜、离线可用多重要、请求多频繁。图片静态资源用缓存优先;核心页面用网络优先保新鲜度;API 数据按业务定。