目录
什么是 Service Worker:
Service Worker 是一段运行在浏览器后台的独立脚本。在用户端,它可以拦截网络请求并决定加载(fetch)什么内容。
Service Workers 主要用于实现后台同步、推送通知等功能,并且通常被用于构建“离线优先”的应用程序,这让开发者能够完全掌控用户体验。
在它出现之前,曾经有一种名为 AppCache 的 API,试图提供离线体验功能。然而,AppCache API 在接口设计上存在诸多问题,而 Service Workers 的出现正是为了解决这些问题。
Service Worker 的生命周期:
Service Worker 的生命周期与网页完全分离。它充当了一个可编程的网络代理,在不使用时会被终止,而在下次需要时重新启动。Service Workers 极度依赖 Javascript Promises 的使用,所以如果你对 Promise 还不熟悉,最好先去了解一下。
在安装阶段,Service Worker 可以缓存一些静态资源,比如网页。如果浏览器成功缓存了这些文件,Service Worker 就会被安装成功。
随后,Service Worker 需要被激活。在激活过程中,Service Worker 可以管理并决定如何处理旧的缓存,通常是删除旧缓存并用新版本替换。
最后,激活完成后,Service Worker 将接管其作用域内的所有页面,除了最初注册 Service Worker 的那个页面,该页面需要手动刷新。Service Worker 在内存使用上非常智能,如果没有需要获取的内容且没有消息事件发生,它就会自动终止。
下面是一张示意图,展示了 Service Worker 在浏览器和网络之间的位置。
!service worker request illustrated
使用 Service Worker 的网站请求链。
前置条件:
- 必须使用 HTTPS(除非是在 localhost 上)
– Service Workers 要求必须使用 HTTPS 连接。在部署之前,Service Workers 可以在 localhost 服务器上正常运行,但如果你想把它上传到互联网上,你需要确保服务器已经配置了 HTTPS。GitHub Pages 是托管免费演示的一个绝佳去处,它们通过 HTTPS 提供服务。
- 浏览器支持
– Chrome、Firefox、Opera、Safari 和 Edge 等主流浏览器对 Service Workers 提供了强有力的支持,这使得它们非常值得部署。
注册:
要使用 Service Worker,首先需要进行注册。这是通过你页面中的 Javascript 代码完成的。一旦 Service Worker 注册成功,浏览器就会开始在后台安装它。
// 确保浏览器支持 Service Worker API
if (navigator.serviceWorker) {
// 在每次页面加载时开始注册流程
window.addEventListener(‘load‘, () => {
navigator.serviceWorker
// register 函数的参数是
// Service Worker 文件的路径
.register(‘/service_worker.js‘)
// 返回注册对象给我们
.then(reg => console.log(‘Service Worker Registered‘))
.catch(swErr => console.log(
`Service Worker Installation Error: ${swErr}}`));
});
}
安装:
注册完成后,Service Worker 需要进行安装,这一步是在 Service Worker 文件中完成的,通常也是你获取并缓存资源的地方。
我们需要执行以下步骤:
- 打开一个缓存
- 缓存资源
- 确认缓存是否成功
var cacheName = ‘geeks-cache-v1‘;
var cacheAssets = [
‘/assets/pages/offline-page.html‘,
‘/assets/styles/offline-page.css‘,
‘/assets/script/offline-page.js‘,
];
// 调用 install 事件
self.addEventListener(‘install‘, e => {
// 等待 Promise 完成
e.waitUntil(
caches.open(cacheName)
.then(cache => {
console.log(`Service Worker: Caching Files: ${cache}`);
cache.addAll(cacheAssets)
// 当所有内容都设置完毕后
.then(() => self.skipWaiting())
})
);
})
激活:
// 调用 Activate 事件
self.addEventListener(‘activate‘, e => {
console.log(‘Service Worker: Activated‘);
// 通过遍历所有缓存来清理旧缓存,
// 删除任何旧缓存或未在列表中定义的缓存
e.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(
cache => {
if (cache !== cacheName) {
console.log(‘Service Worker: Deleting Old Cache: ‘ + cache);
return caches.delete(cache);
}
}
)
);
})
);
});
拦截 Fetch 请求:
// 调用 Fetch 事件
self.addEventListener(‘fetch‘, e => {
console.log(‘Service Worker: Fetching‘);
e.respondWith(
fetch(e.request)
.then(res => {
// 请求成功,将响应克隆一份存入缓存
const resClone = res.clone();
caches.open(cacheName).then(cache => {
cache.put(e.request, resClone);
});
return res;
})
.catch(err => caches.match(e.request).then(res => res))
);
});
在 2026 年的今天,仅理解这些基础概念已经不足以构建顶级的应用。让我们深入探讨一下在现代开发环境中,我们如何更高级地运用这些技术。
2026 年的现代开发范式与最佳实践
在我们深入编写代码之前,让我们重新审视一下开发环境的变化。如今,我们很少再从零开始手写每一行 Service Worker 代码。作为开发者,我们现在所处的时代被称为“氛围编程”的时代——利用 AI 辅助工具(如 Cursor, Windsurf 或 GitHub Copilot)来加速原型开发。但在生产环境中,我们依然需要对底层逻辑有深刻的掌控。
为什么我们需要更高级的策略?
早期的 Service Worker 实现往往采用“缓存优先”策略,即只要有缓存就返回缓存。这在当时对于静态页面是有效的,但在 2026 年,我们的应用大多是动态的、实时的。如果我们一直返回旧的缓存,用户可能会看到几天前的新闻或过期的数据。
因此,我们需要引入更智能的 Stale-While-Revalidate(过期后更新) 策略,甚至针对不同类型的 API 请求采用混合策略。
生产级代码重构:模块化与 Workbox
在现代开发中,我们通常不会像上面的例子那样直接操作 INLINECODEd1cf7c91 API。维护那些手写的 INLINECODEb30899a2 数组是一场噩梦。目前业界的标准做法是使用 Google 的 Workbox 库,或者在其基础上构建自己的抽象层。让我们来看看如何用更现代、更健壮的方式重写上面的逻辑。
#### 1. 动态预缓存
我们不再手动列出文件,而是利用构建工具(如 Vite 或 Webpack)生成的清单。这消除了“我更新了文件但忘记更新 Service Worker 数组”这种人为错误。
// sw.js (现代版本)
import { precacheAndRoute } from ‘workbox-precaching‘;
// 使用注入注记:构建工具会自动生成 __WB_MANIFEST 数组
precacheAndRoute(self.__WB_MANIFEST);
#### 2. 智能路由策略
在之前的 fetch 事件示例中,我们简单地尝试 fetch,失败则回退到缓存。这在生产环境是不够的。我们需要根据请求的内容类型来决定策略。
让我们想象一个场景:你正在开发一个电商应用。对于图片,你希望用户瞬间看到内容(缓存优先);但对于价格和库存,你必须获取最新数据(网络优先),同时为了防止网络抖动,你要保留旧数据作为降级方案。
import { registerRoute, NavigationRoute, Route } from ‘workbox-routing‘;
import {
StaleWhileRevalidate,
NetworkFirst,
CacheFirst
} from ‘workbox-strategies‘;
import { ExpirationPlugin } from ‘workbox-expiration‘;
// 场景 A:图片资源 (缓存优先,因为图片很少变化,且流量大)
// 我们使用 CacheFirst 并配合 ExpirationPlugin 限制缓存数量,防止爆满
registerRoute(
({ request }) => request.destination === ‘image‘,
new CacheFirst({
cacheName: ‘image-cache‘,
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
}),
],
})
);
// 场景 B:API 调用 (网络优先,但支持降级)
// 对于获取实时数据的请求,我们优先尝试网络
registerRoute(
({ url }) => url.pathname.startsWith(‘/api/‘),
new NetworkFirst({
cacheName: ‘api-cache‘,
networkTimeoutSeconds: 3, // 3秒后如果网络没响应,回退到缓存
plugins: [
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 5 * 60, // API 数据只缓存 5 分钟
}),
],
})
);
// 场景 C:静态 JS/CSS (Stale While Revalidate)
// 这是最平衡的策略:快速返回缓存,同时在后台更新
registerRoute(
({ request }) =>
request.destination === ‘script‘ ||
request.destination === ‘style‘,
new StaleWhileRevalidate({
cacheName: ‘static-resources‘,
})
);
边界情况与容灾处理
在我们最近的一个大型企业级后台管理系统中,我们遇到了一个棘手的问题:用户在登录状态过期时,Service Worker 缓存了错误的 302 重定向响应,导致用户陷入死循环,无法跳转到登录页。
这就引出了我们在 2026 年必须注意的一点:不要缓存非成功的响应。除非你非常清楚自己在做什么,否则只缓存状态码为 200 的响应。
此外,随着 Agentic AI 的兴起,我们的应用架构正在发生变化。有些 AI 推理任务可能会在 Service Worker 的 fetch 事件中进行预判或拦截,以实现更智能的数据预取。比如,如果检测到用户鼠标悬停在某个链接上,Service Worker 可以提前发起请求。
性能优化与监控
你不能优化你无法衡量的东西。在 2026 年,我们不仅仅关注缓存命中率,我们关注的是“Time to Interactive”(可交互时间)和“First Contentful Paint”(首次内容绘制)。
我们建议在 Service Worker 中集成遥测功能:
self.addEventListener(‘fetch‘, (event) => {
const start = Date.now();
event.respondWith(
(async () => {
try {
const response = await fetch(event.request);
// 记录网络请求耗时
const duration = Date.now() - start;
if (duration > 1000) {
// 使用 sendBeacon 向分析服务器上报慢请求
navigator.sendBeacon(‘/analytics‘, JSON.stringify({
url: event.request.url,
duration: duration,
sw_cache: ‘network‘
}));
}
return response;
} catch (error) {
const cache = await caches.open(‘dynamic-cache‘);
const cachedResponse = await cache.match(event.request);
if (cachedResponse) {
// 即使是缓存命中,也要记录“救命”的事实
navigator.sendBeacon(‘/analytics‘, JSON.stringify({
url: event.request.url,
sw_cache: ‘fallback‘
}));
return cachedResponse;
}
throw error;
}
})()
);
});
部署与更新策略
在 2026 年的微前端架构中,Service Worker 的作用域管理变得极其重要。如果你的主应用和子应用都注册了 Service Worker,它们可能会发生冲突。
我们的最佳实践是:由主应用(Shell)统一管理 Service Worker,子应用通过向主应用发送消息来请求特定的资源预缓存。这种“中心化管理”模式避免了不同版本的 Service Worker 争夺网络控制权的问题。
替代方案与未来展望
虽然 Service Worker 依然是离线能力的王者,但我们也看到了 Speculation Rules API 的兴起。这个 API 允许开发者直接告诉浏览器哪些页面可能会被访问,从而进行预渲染或预连接,这在某些高流量场景下比 Service Worker 更轻量级。
然而,对于需要完全自定义网络逻辑(例如离线编辑、本地数据库同步)的场景,Service Worker 依然是不可替代的。特别是结合 IndexedDB 和 WebAssembly,我们甚至可以在 Service Worker 中运行复杂的本地数据处理逻辑,构建真正的“Serverless”客户端。
总结
Service Workers 已经从一个实验性的 API 演变为现代 Web 的基石。通过结合 2026 年的工程化工具、AI 辅助开发流程以及更精细的缓存策略,我们能够构建出比原生应用更流畅、更可靠的 Web 体验。希望这篇文章不仅让你理解了 Service Worker 的基础,更让你掌握了如何在实际的大型项目中驾驭它。
让我们继续探索,看看在边缘计算与 AI 赋能的 Web 3.0 时代,我们还能利用浏览器做哪些惊人的事情。