JavaScript中的Service Worker与离线缓存
字数 929 2025-11-08 21:37:44
JavaScript中的Service Worker与离线缓存
Service Worker是一种在浏览器后台运行的脚本,它独立于网页,能够拦截和处理网络请求,实现缓存管理、推送通知等高级功能。今天我将详细讲解Service Worker的工作原理、生命周期和实际应用。
1. Service Worker的基本概念
Service Worker本质上是一个JavaScript工作线程,但它与普通Web Worker不同:
- 拥有独立的执行上下文,无法直接访问DOM
- 需要运行在HTTPS环境(localhost开发环境除外)
- 完全异步,大量使用Promise
- 可以控制页面网络请求,实现精细缓存策略
2. Service Worker的生命周期
Service Worker有明确的生命周期阶段,理解这些阶段至关重要:
2.1 注册(Registration)
// 在主线程中注册Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('注册成功,作用域为:', registration.scope);
})
.catch(error => {
console.log('注册失败:', error);
});
}
注册时需要指定Service Worker脚本路径,浏览器会自动判断作用域范围。
2.2 安装(Install)
// 在sw.js中监听install事件
const CACHE_NAME = 'v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
self.addEventListener('install', event => {
// 执行安装步骤
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('缓存已打开');
return cache.addAll(urlsToCache);
})
);
});
安装阶段通常用于预缓存关键资源。event.waitUntil()确保Service Worker在缓存完成前不会进入下一阶段。
2.3 激活(Activation)
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
// 删除旧版本缓存
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});
激活阶段常用于清理旧缓存。此时新的Service Worker开始控制页面,但旧版本仍在运行直到所有标签页关闭。
3. 请求拦截与缓存策略
Service Worker最核心的功能是拦截网络请求:
3.1 基本的缓存优先策略
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中则返回缓存,否则发起网络请求
if (response) {
return response;
}
return fetch(event.request);
})
);
});
3.2 更复杂的缓存策略
// 网络优先,失败时回退缓存
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
// 网络请求成功,更新缓存
return caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, response.clone());
return response;
});
})
.catch(() => {
// 网络失败,使用缓存
return caches.match(event.request);
})
);
});
4. 版本管理与更新
Service Worker的更新机制需要特别注意:
4.1 检测更新
// 在主线程中定期检查更新
navigator.serviceWorker.ready.then(registration => {
registration.update(); // 手动触发更新检查
});
4.2 跳过等待阶段
// 在install事件中跳过等待,立即激活
self.addEventListener('install', event => {
self.skipWaiting(); // 立即激活新版本
event.waitUntil(
// 缓存资源...
);
});
5. 实际应用示例
让我们实现一个完整的离线应用:
5.1 完整的Service Worker实现
// sw.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/styles/app.css',
'/scripts/app.js',
'/images/logo.png'
];
// 安装阶段
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
.then(() => self.skipWaiting()) // 立即激活
);
});
// 激活阶段
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.map(key => {
if (key !== CACHE_NAME) {
return caches.delete(key);
}
}))
).then(() => self.clients.claim()) // 立即控制所有页面
);
});
// 请求处理
self.addEventListener('fetch', event => {
// 只处理GET请求
if (event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// 始终发起网络请求更新缓存
const fetchPromise = fetch(event.request)
.then(networkResponse => {
// 克隆响应用于缓存
const responseClone = networkResponse.clone();
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, responseClone));
return networkResponse;
});
// 返回缓存或网络响应
return cachedResponse || fetchPromise;
})
);
});
6. 调试与最佳实践
- 使用Chrome DevTools的Application面板调试
- 合理设置缓存策略,避免存储过大文件
- 及时清理旧缓存,避免存储空间浪费
- 考虑实现缓存过期机制
Service Worker为Web应用提供了真正的离线能力,是现代PWA(渐进式Web应用)的核心技术。通过合理运用缓存策略,可以显著提升用户体验。