Service Worker是运行在浏览器后台的脚本,独立于主线程,能够拦截和处理网络请求,是实现PWA离线可用能力的核心。它可以在用户首次访问页面时缓存静态资源和接口数据,后续无网络时直接从缓存中读取内容,保证应用正常使用。

Service Worker的基础概念
Service Worker本质上是一个事件驱动的worker,它不能直接操作DOM,也不能访问window对象,但是可以通过postMessage方法和主线程通信。它的生命周期包括注册、安装、激活、拦截请求等阶段,每个阶段都有对应的事件可以监听处理。
核心生命周期阶段
- 注册阶段:主线程通过
navigator.serviceWorker.register方法注册Service Worker脚本,浏览器会在后台下载并执行脚本。 - 安装阶段:注册成功后触发
install事件,这个阶段适合预缓存应用的核心静态资源,比如HTML、CSS、JS、图片等。 - 激活阶段:安装完成后触发
activate事件,这个阶段可以清理旧的缓存,保证缓存内容的版本一致性。 - 拦截请求阶段:激活后的Service Worker会拦截页面发出的网络请求,开发者可以在
fetch事件中自定义请求处理逻辑,比如优先使用缓存还是优先请求网络。
实现离线PWA的完整步骤
第一步:注册Service Worker
首先需要在主页面的入口JS中注册Service Worker,注意Service Worker脚本必须运行在HTTPS环境或者localhost本地环境,否则浏览器会拒绝注册。
// 检查浏览器是否支持Service Worker
if ('serviceWorker' in navigator) {
// 页面加载完成后注册
window.addEventListener('load', () => {
// 注册sw.js作为Service Worker脚本,作用域为当前目录
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('Service Worker注册成功,作用域为:', registration.scope);
})
.catch((error) => {
console.log('Service Worker注册失败:', error);
});
});
}
第二步:编写Service Worker脚本实现缓存
创建sw.js文件,在里面编写安装、激活、请求拦截的逻辑,实现资源的缓存和读取。
// 定义缓存名称和需要预缓存的资源列表
const CACHE_NAME = 'pwa-cache-v1';
const PRE_CACHE_URLS = [
'/',
'/index.html',
'/style.css',
'/main.js',
'/logo.png'
];
// 监听安装事件,预缓存核心资源
self.addEventListener('install', (event) => {
// 等待缓存操作完成后再结束安装阶段
event.waitUntil(
// 打开指定名称的缓存
caches.open(CACHE_NAME)
.then((cache) => {
console.log('开始预缓存资源');
// 将预定义的资源列表添加到缓存中
return cache.addAll(PRE_CACHE_URLS);
})
.then(() => {
console.log('预缓存完成');
// 强制跳过等待,直接进入激活阶段
return self.skipWaiting();
})
);
});
// 监听激活事件,清理旧版本缓存
self.addEventListener('activate', (event) => {
event.waitUntil(
// 获取所有缓存的名称
caches.keys()
.then((cacheNames) => {
// 遍历所有缓存,删除不是当前版本的缓存
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
})
.then(() => {
// 让Service Worker立即控制所有当前打开的页面
return self.clients.claim();
})
);
});
// 监听fetch事件,拦截网络请求,实现缓存优先策略
self.addEventListener('fetch', (event) => {
// 只处理GET请求,非GET请求直接走网络
if (event.request.method !== 'GET') {
return;
}
// 用缓存优先的策略处理请求
event.respondWith(
// 先查找缓存中是否有对应的请求结果
caches.match(event.request)
.then((cachedResponse) => {
// 如果缓存中有结果,直接返回缓存内容
if (cachedResponse) {
console.log('从缓存中读取:', event.request.url);
return cachedResponse;
}
// 缓存中没有结果,发起网络请求
console.log('发起网络请求:', event.request.url);
return fetch(event.request)
.then((networkResponse) => {
// 网络请求成功后,将结果克隆一份存入缓存,再返回给页面
// 克隆是因为response只能被读取一次
const responseClone = networkResponse.clone();
caches.open(CACHE_NAME)
.then((cache) => {
cache.put(event.request, responseClone);
});
return networkResponse;
})
.catch(() => {
// 网络请求失败,且缓存中没有内容,返回默认的错误页面(可选)
// 这里可以根据需求返回离线提示页面
return new Response('当前无网络,且资源未缓存,无法访问', {
status: 503,
statusText: 'Service Unavailable'
});
});
})
);
});
第三步:添加PWA必需的清单文件
要实现完整的PWA能力,还需要添加manifest.json文件,声明应用的名称、图标、启动方式等信息,让浏览器识别这是一个PWA应用。
{
"name": "离线PWA示例",
"short_name": "PWA示例",
"description": "使用Service Worker实现的离线可用PWA应用",
"start_url": "/index.html",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
然后在HTML页面的<head>标签中添加清单文件的引用:
<link rel="manifest" href="/manifest.json">
缓存策略的选择
上面的示例使用了缓存优先的策略,实际开发中可以根据资源类型选择不同的缓存策略:
| 策略名称 | 适用场景 | 实现逻辑 |
|---|---|---|
| 缓存优先 | 静态资源、不常变化的接口 | 先查缓存,缓存有则返回,无则请求网络并缓存 |
| 网络优先 | 实时性要求高的接口 | 先请求网络,成功则返回并缓存,失败则查缓存 |
| 仅缓存 | 完全离线的资源 | 只从缓存读取,不接受网络请求 |
| 仅网络 | 不需要离线的资源 | 只走网络请求,不缓存 |
注意事项
- Service Worker的更新需要修改脚本内容,浏览器检测到脚本变化后会重新安装新版本,新版本激活后会控制新的页面,旧的页面仍然由旧版本的Service Worker控制,直到页面重新加载。
- 缓存的资源需要定期更新,避免用户一直使用旧版本的内容,可以在激活阶段或者fetch事件中做版本校验。
- 不要缓存太大的资源,避免占用过多的用户存储空间,浏览器对单个域名的缓存大小有限制。
- 本地开发时使用localhost可以正常调试Service Worker,部署到线上必须使用HTTPS协议,否则Service Worker无法注册生效。
Service Worker的功能不止于离线缓存,还可以实现消息推送、后台同步等能力,结合PWA的其他特性,可以让网页应用拥有接近原生应用的使用体验。
Service_WorkerPWA离线缓存前端开发修改时间:2026-06-15 04:27:42