Service Worker 是什么
Service Worker 是浏览器里独立于页面的后台脚本,可以拦截页面发出的网络请求,决定返回缓存还是走网络。它让前端第一次有能力「自己控制网络」,这也是 PWA(渐进式 Web 应用)的核心底座。
它能做到三件事:离线可用(断网也能打开上次访问过的页面)、秒开体验(资源命中缓存免下载)、可控更新(缓存策略自己定,新版本可控发布)。对内容型站点,离线缓存的意义尤其大。
限制要清楚:Service Worker 只能跑在 HTTPS 或 localhost 下;不能直接访问 DOM;生命周期由浏览器管理,随时可能被杀;更新不是即时的。这些限制决定了它的实现方式——事件驱动、无状态、可重启。
很多人把 Service Worker 当成「缓存插件」,其实它是可编程的网络代理。缓存只是它最常用的能力,配合消息推送、后台同步,它能做的事情更多。
Service Worker 是前端拿到的一张「网络代理票」。
- 生命周期install、activate、fetch 三事件。
- 预缓存安装时缓存静态资源。
- 运行时缓存请求拦截按策略缓存。
- Workbox官方工具箱简化开发。
注册与生命周期
页面里注册 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 数据按业务定。
离线与更新控制
离线时给用户一个兜底页,而不是浏览器默认的「无法连接」。注册 fetch 时,网络失败回退到缓存的 offline 页。
async function networkFirst(request) {
try {
const res = await fetch(request);
const copy = res.clone();
caches.open("pages").then(c => c.put(request, copy));
return res;
} catch (err) {
const cached = await caches.match(request);
if (cached) return cached;
return caches.match("/offline.html"); // 离线兜底页
}
}
更新控制:缓存版本号每次发版 +1(v1 到 v2),activate 时删掉旧版本缓存。新版本要等当前页面关闭才接管,所以页面里监听 updatefound,提示用户「有新版本,刷新后生效」。别自动强制刷新,会打断用户操作。
Workbox 简化实践
| 能力 | Workbox 工具 |
|---|---|
| 预缓存 | precacheAndRoute + 构建生成 manifest |
| 运行时缓存 | registerRoute + 内置策略 |
| 更新提示 | workbox-window 的 updatefound |
| 后台同步 | BackgroundSyncPlugin |
// workbox 构建配置(vite-plugin-pwa 或 workbox-cli)
import { precacheAndRoute } from "workbox-precaching";
import { registerRoute, CacheFirst, NetworkFirst } from "workbox-routing";
precacheAndRoute(self.__WB_MANIFEST);
registerRoute(
({ request }) => request.destination === "image",
new CacheFirst({ cacheName: "images" })
);
registerRoute(
({ request }) => request.mode === "navigate",
new NetworkFirst({ cacheName: "pages" })
);
手写 sw.js 适合学习,生产直接用 Workbox 省心。
Service Worker 实战清单
- 仅 HTTPS 环境使用,本地开发用 localhost。
- 静态资源预缓存,版本号更新时清理旧缓存。
- 按资源类型选策略:图片缓存优先,页面网络优先。
- 离线兜底页 + 更新提示,别打断用户。
- 生产用 Workbox,manifest 自动生成。
缓存策略选对,离线是能力;选错,更新是灾难。