pin_drop当前位置:知识文库 ❯ 图文
高级技巧:HTML Service Wo - 详细教程与实战指南
教程简介
Service Worker是现代Web开发中最强大的API之一,它充当浏览器和网络之间的代理服务器,可以拦截网络请求、管理缓存、实现离线体验和推送通知。Service Worker是PWA(渐进式Web应用)的核心技术,使Web应用具备了媲美原生应用的能力。本教程将系统讲解Service Worker的生命周期、注册与安装、Fetch事件拦截、Cache API、离线策略、后台同步和推送通知。
核心概念
Service Worker特点
Service Worker生命周期
语法与用法
注册Service Worker
代码示例
// 在主页面中注册
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js', {
scope: '/' // 作用域
}).then(function(registration) {
console.log('Service Worker注册成功,作用域:', registration.scope);
// 检测更新
registration.addEventListener('updatefound', function() {
const newWorker = registration.installing;
console.log('发现新版本:', newWorker.state);
newWorker.addEventListener('statechange', function() {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// 新版本已安装,提示用户刷新
console.log('新版本可用,请刷新页面');
}
});
});
}).catch(function(error) {
console.log('Service Worker注册失败:', error);
});
});
}安装与激活
代码示例
// sw.js - Service Worker脚本
const CACHE_NAME = 'my-app-v1';
const CACHE_URLS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/offline.html'
];
// 安装事件
self.addEventListener('install', function(event) {
console.log('Service Worker安装中...');
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('缓存文件中...');
return cache.addAll(CACHE_URLS);
})
.then(function() {
// 跳过等待,立即激活
return self.skipWaiting();
})
);
});
// 激活事件
self.addEventListener('activate', function(event) {
console.log('Service Worker激活中...');
event.waitUntil(
// 清理旧缓存
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheName !== CACHE_NAME) {
console.log('删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
}).then(function() {
// 立即控制所有客户端
return self.clients.claim();
})
);
});Fetch事件拦截
代码示例
// 拦截网络请求
self.addEventListener('fetch', function(event) {
console.log('拦截请求:', event.request.url);
event.respondWith(
// 先尝试网络,失败则使用缓存
fetch(event.request)
.then(function(response) {
// 网络成功,缓存响应
if (response.ok) {
const responseClone = response.clone();
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, responseClone);
});
}
return response;
})
.catch(function() {
// 网络失败,使用缓存
return caches.match(event.request).then(function(cachedResponse) {
if (cachedResponse) {
return cachedResponse;
}
// 如果是导航请求,返回离线页面
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
return new Response('离线', { status: 503 });
});
})
);
});Cache API
代码示例
// 打开缓存
caches.open('my-cache').then(function(cache) { ... });
// 添加缓存
cache.add('/page.html'); // 请求并缓存
cache.addAll(['/page1.html', '/page2.html']); // 批量请求并缓存
cache.put(request, response); // 缓存指定的请求/响应对
// 查找缓存
cache.match(request); // 查找匹配的缓存
caches.match(request); // 在所有缓存中查找
// 删除缓存
cache.delete(request); // 删除指定缓存项
caches.delete('my-cache'); // 删除整个缓存
// 获取缓存键
cache.keys(); // 获取所有缓存请求
caches.keys(); // 获取所有缓存名称离线策略
代码示例
// Cache First策略
function cacheFirst(event) {
event.respondWith(
caches.match(event.request).then(function(cachedResponse) {
return cachedResponse || fetch(event.request).then(function(response) {
const responseClone = response.clone();
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, responseClone);
});
return response;
});
})
);
}
// Network First策略
function networkFirst(event) {
event.respondWith(
fetch(event.request).then(function(response) {
const responseClone = response.clone();
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, responseClone);
});
return response;
}).catch(function() {
return caches.match(event.request);
})
);
}
// Stale While Revalidate策略
function staleWhileRevalidate(event) {
event.respondWith(
caches.match(event.request).then(function(cachedResponse) {
const fetchPromise = fetch(event.request).then(function(response) {
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, response);
});
return response;
});
return cachedResponse || fetchPromise;
})
);
}后台同步
代码示例
// 主页面:注册同步事件
navigator.serviceWorker.ready.then(function(registration) {
return registration.sync.register('sync-data');
});
// Service Worker:处理同步
self.addEventListener('sync', function(event) {
if (event.tag === 'sync-data') {
event.waitUntil(
// 执行同步操作
fetch('/api/sync', {
method: 'POST',
body: JSON.stringify(getPendingData())
})
);
}
});推送通知
代码示例
// 主页面:请求通知权限并订阅
async function subscribePush() {
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: 'YOUR_PUBLIC_KEY'
});
// 将subscription发送到服务器
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription)
});
}
// Service Worker:处理推送
self.addEventListener('push', function(event) {
const data = event.data ? event.data.json() : {};
const title = data.title || '新消息';
const options = {
body: data.body || '',
icon: '/icon-192.png',
badge: '/badge-72.png',
data: data.url || '/'
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
// Service Worker:通知点击
self.addEventListener('notificationclick', function(event) {
event.notification.close();
event.waitUntil(
clients.openWindow(event.notification.data)
);
});代码示例
示例1:完整的离线应用
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>离线应用示例</title>
<link rel="manifest" href="manifest.json">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.status-bar {
padding: 0.75rem 1rem;
border-radius: 4px;
margin-bottom: 1rem;
font-weight: 600;
}
.online { background: #d4edda; color: #155724; }
.offline { background: #f8d7da; color: #721c24; }
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
}
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
background: #4A90D9;
color: white;
margin: 0.25rem;
}
</style>
</head>
<body>
<div id="statusBar" class="status-bar online">在线</div>
<h1>离线应用示例</h1>
<div class="card">
<h2>Service Worker状态</h2>
<p id="swStatus">检测中...</p>
<button onclick="registerSW()">注册Service Worker</button>
<button onclick="unregisterSW()">注销Service Worker</button>
</div>
<div class="card">
<h2>缓存管理</h2>
<div id="cacheInfo">加载中...</div>
<button onclick="updateCache()">更新缓存</button>
<button onclick="clearCache()">清除缓存</button>
</div>
<script>
// 网络状态监听
function updateOnlineStatus() {
const bar = document.getElementById('statusBar');
if (navigator.onLine) {
bar.className = 'status-bar online';
bar.textContent = '在线';
} else {
bar.className = 'status-bar offline';
bar.textContent = '离线 - 使用缓存数据';
}
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
updateOnlineStatus();
// 注册Service Worker
async function registerSW() {
if (!('serviceWorker' in navigator)) {
document.getElementById('swStatus').textContent = '浏览器不支持Service Worker';
return;
}
try {
// 注意:实际使用时需要创建sw.js文件
// 此处仅演示注册逻辑
document.getElementById('swStatus').textContent = 'Service Worker需要HTTPS环境和sw.js文件才能运行';
} catch (error) {
document.getElementById('swStatus').textContent = '注册失败:' + error.message;
}
}
async function unregisterSW() {
const registration = await navigator.serviceWorker.getRegistration();
if (registration) {
await registration.unregister();
document.getElementById('swStatus').textContent = 'Service Worker已注销';
}
}
// 缓存管理
async function showCacheInfo() {
if (!('caches' in window)) {
document.getElementById('cacheInfo').textContent = '浏览器不支持Cache API';
return;
}
const names = await caches.keys();
if (names.length === 0) {
document.getElementById('cacheInfo').textContent = '暂无缓存';
return;
}
let info = '缓存列表:';
for (const name of names) {
const cache = await caches.open(name);
const keys = await cache.keys();
info += `\n${name} (${keys.length}个文件)`;
}
document.getElementById('cacheInfo').textContent = info;
}
async function clearCache() {
const names = await caches.keys();
await Promise.all(names.map(name => caches.delete(name)));
showCacheInfo();
}
async function updateCache() {
showCacheInfo();
}
showCacheInfo();
</script>
</body>
</html>浏览器兼容性
注意事项与最佳实践
必须使用HTTPS:Service Worker只能运行在HTTPS环境下(localhost除外)
注意作用域:Service Worker默认的作用域是其所在目录及子目录
合理使用缓存策略:静态资源用Cache First,动态数据用Network First
版本化缓存名称:使用版本号命名缓存(如my-app-v1),方便更新
清理旧缓存:在activate事件中删除旧版本缓存
优雅降级:检测Service Worker支持后再注册,不支持时正常工作
避免缓存POST请求:只缓存GET请求,POST请求不应缓存
处理安装失败:install事件中缓存失败会导致Service Worker安装失败
更新策略:浏览器会自动检测SW文件变化,但需要关闭所有标签页才能激活新版本
调试工具:使用Chrome DevTools的Application面板调试Service Worker
代码规范示例
代码示例
// 规范:Service Worker模板
const CACHE_NAME = 'my-app-v1';
const STATIC_URLS = ['/', '/styles.css', '/app.js'];
const DYNAMIC_CACHE = 'my-app-dynamic-v1';
// 安装:预缓存静态资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_URLS))
.then(() => self.skipWaiting())
);
});
// 激活:清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE_NAME && k !== DYNAMIC_CACHE)
.map(k => caches.delete(k)))
).then(() => self.clients.claim())
);
});
// Fetch:策略路由
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// 静态资源:Cache First
if (STATIC_URLS.includes(url.pathname)) {
event.respondWith(cacheFirst(event.request));
return;
}
// API请求:Network First
if (url.pathname.startsWith('/api/')) {
event.respondWith(networkFirst(event.request));
return;
}
// 其他:Stale While Revalidate
event.respondWith(staleWhileRevalidate(event.request));
});常见问题与解决方案
问题1:Service Worker更新不生效
解决方案:使用skipWaiting和clients.claim强制更新。
代码示例
self.addEventListener('install', event => {
event.waitUntil(
cacheResources().then(() => self.skipWaiting())
);
});
self.addEventListener('activate', event => {
event.waitUntil(
cleanOldCaches().then(() => self.clients.claim())
);
});问题2:缓存了错误的响应
解决方案:只缓存成功的响应。
代码示例
fetch(request).then(response => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
});问题3:离线时导航请求返回空白
解决方案:为导航请求提供离线回退页面。
代码示例
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => caches.match('/offline.html'))
);
}总结
Service Worker是现代Web应用的核心技术,赋予了Web应用离线能力和后台处理能力。通过本教程的学习,你应该掌握了:
生命周期:install、activate、fetch等事件的正确处理
Cache API:缓存的增删改查操作
离线策略:Cache First、Network First、Stale While Revalidate
后台同步:使用sync事件处理离线数据同步
推送通知:Push API和Notification API的使用
Service Worker使Web应用从"必须在线"进化到"离线可用",是PWA的核心基础。
常见问题
问题1:Service Worker更新不生效?
使用skipWaiting和clients.claim强制更新。
问题2:缓存了错误的响应?
只缓存成功的响应。
问题3:离线时导航请求返回空白?
为导航请求提供离线回退页面。
本文由小确幸生活整理发布,转载请注明出处
本文涉及AI创作
内容由AI创作,请仔细甄别