JavaScript系列(78)--Service Worker 深入解析
Service Worker 深入解析 🔄
Service Worker 是一种运行在浏览器后台的脚本,它为现代Web应用提供了强大的离线体验、后台同步和推送通知等功能。让我们深入了解这项强大的技术。
Service Worker 概述 🌟
💡 小知识:Service Worker 是一个独立于网页的JavaScript线程,它不能直接访问DOM,但可以通过postMessage与页面通信。它的设计目标是创建更好的离线体验,实现PWA(Progressive Web Apps)应用。
生命周期详解 📊
// 1. 注册Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration.scope);
})
.catch(error => {
console.error('SW registration failed:', error);
});
});
}
// 2. Service Worker 脚本 (sw.js)
const CACHE_NAME = 'app-v1';
const CACHE_URLS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/images/logo.png'
];
// 安装阶段
self.addEventListener('install', event => {
console.log('Service Worker installing...');
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(CACHE_URLS);
})
);
});
// 激活阶段
self.addEventListener('activate', event => {
console.log('Service Worker activating...');
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});
缓存策略实现 🎯
// 1. 缓存优先策略
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response; // 返回缓存的响应
}
return fetch(event.request); // 如果没有缓存,发起网络请求
})
);
});
// 2. 网络优先策略
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.catch(() => {
return caches.match(event.request);
})
);
});
// 3. 缓存优先,网络更新策略
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 返回缓存同时更新缓存
const fetchPromise = fetch(event.request).then(networkResponse => {
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});
return response || fetchPromise;
})
);
});
// 4. 自定义缓存策略
class CacheStrategy {
constructor(cacheName) {
this.cacheName = cacheName;
}
async cacheFirst(request) {
const cache = await caches.open(this.cacheName);
const cached = await cache.match(request);
if (cached) {
return cached;
}
const fetched = await fetch(request);
await cache.put(request, fetched.clone());
return fetched;
}
async networkFirst(request) {
try {
const fetched = await fetch(request);
const cache = await caches.open(this.cacheName);
await cache.put(request, fetched.clone());
return fetched;
} catch (error) {
const cached = await caches.match(request);
if (cached) {
return cached;
}
throw error;
}
}
}
推送通知实现 🔔
// 1. 请求通知权限
function requestNotificationPermission() {
return Notification.requestPermission()
.then(permission => {
if (permission === 'granted') {
return true;
}
throw new Error('Notification permission denied');
});
}
// 2. 订阅推送服务
async function subscribePushNotification() {
const registration = await navigator.serviceWorker.ready;
// 生成VAPID密钥对
const vapidPublicKey = 'YOUR_PUBLIC_VAPID_KEY';
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
});
// 发送订阅信息到服务器
await fetch('/api/push/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
});
}
// 3. 处理推送消息
self.addEventListener('push', event => {
const options = {
body: event.data.text(),
icon: '/images/icon.png',
badge: '/images/badge.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{
action: 'explore',
title: '查看详情'
},
{
action: 'close',
title: '关闭'
}
]
};
event.waitUntil(
self.registration.showNotification('推送通知', options)
);
});
// 4. 处理通知点击
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'explore') {
event.waitUntil(
clients.openWindow('/details')
);
}
});
后台同步实现 🔄
// 1. 注册后台同步
async function registerBackgroundSync() {
const registration = await navigator.serviceWorker.ready;
try {
await registration.sync.register('syncData');
console.log('Background sync registered');
} catch (error) {
console.log('Background sync registration failed:', error);
}
}
// 2. 处理同步事件
self.addEventListener('sync', event => {
if (event.tag === 'syncData') {
event.waitUntil(
syncData()
);
}
});
// 3. 同步数据实现
async function syncData() {
try {
// 从IndexedDB获取待同步数据
const db = await openDatabase();
const data = await getUnsynedData(db);
// 发送数据到服务器
for (const item of data) {
await fetch('/api/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
});
// 更新同步状态
await markAsSynced(db, item.id);
}
} catch (error) {
console.error('Sync failed:', error);
throw error; // 重试同步
}
}
离线存储实现 💾
// 1. IndexedDB 存储
class IndexedDBStorage {
constructor(dbName, version) {
this.dbName = dbName;
this.version = version;
}
async openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = event => {
const db = event.target.result;
// 创建存储对象
if (!db.objectStoreNames.contains('offlineData')) {
db.createObjectStore('offlineData', {
keyPath: 'id',
autoIncrement: true
});
}
};
});
}
async saveData(data) {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['offlineData'], 'readwrite');
const store = transaction.objectStore('offlineData');
const request = store.add(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async getData(id) {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['offlineData'], 'readonly');
const store = transaction.objectStore('offlineData');
const request = store.get(id);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
// 2. Cache Storage API 使用
class CacheStorage {
constructor(cacheName) {
this.cacheName = cacheName;
}
async saveResponse(request, response) {
const cache = await caches.open(this.cacheName);
return cache.put(request, response);
}
async getResponse(request) {
const cache = await caches.open(this.cacheName);
return cache.match(request);
}
async deleteCache() {
return caches.delete(this.cacheName);
}
}
安全考虑 🔒
// 1. 安全检查工具
class SecurityChecker {
static checkSSL() {
if (location.protocol !== 'https:') {
console.warn('Service Worker requires HTTPS');
return false;
}
return true;
}
static validateSource(request) {
const url = new URL(request.url);
// 检查请求源
if (!url.protocol.startsWith('https')) {
return false;
}
// 检查允许的域名
const allowedDomains = [
'api.example.com',
'cdn.example.com'
];
return allowedDomains.includes(url.hostname);
}
}
// 2. 安全的缓存策略
self.addEventListener('fetch', event => {
// 验证请求源
if (!SecurityChecker.validateSource(event.request)) {
return;
}
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
// 验证缓存响应
if (isValidResponse(response)) {
return response;
}
}
return fetch(event.request);
})
);
});
// 3. 响应验证
function isValidResponse(response) {
// 检查响应状态
if (!response.ok) {
return false;
}
// 检查内容类型
const contentType = response.headers.get('content-type');
const allowedTypes = [
'text/html',
'text/css',
'application/javascript',
'image/jpeg',
'image/png'
];
return allowedTypes.some(type => contentType?.includes(type));
}
调试技巧 🔍
// 1. 开发模式检测
const isDev = process.env.NODE_ENV === 'development';
// 2. 调试日志工具
class ServiceWorkerLogger {
constructor(enabled = isDev) {
this.enabled = enabled;
}
log(...args) {
if (this.enabled) {
console.log('[SW]', ...args);
}
}
error(...args) {
if (this.enabled) {
console.error('[SW Error]', ...args);
}
}
group(label) {
if (this.enabled) {
console.group(`[SW] ${label}`);
}
}
groupEnd() {
if (this.enabled) {
console.groupEnd();
}
}
}
const logger = new ServiceWorkerLogger();
// 3. 性能监控
class PerformanceMonitor {
static async measureCacheOperation(operation) {
const start = performance.now();
try {
await operation();
} finally {
const duration = performance.now() - start;
logger.log(`Cache operation took ${duration}ms`);
}
}
static async measureNetworkRequest(request) {
const start = performance.now();
try {
const response = await fetch(request);
const duration = performance.now() - start;
logger.log(`Network request to ${request.url} took ${duration}ms`);
return response;
} catch (error) {
logger.error(`Network request failed:`, error);
throw error;
}
}
}
结语 📝
Service Worker 为现代Web应用提供了强大的离线能力和后台处理能力。我们学习了:
- Service Worker 的生命周期和注册过程
- 各种缓存策略的实现
- 推送通知的处理
- 后台同步的实现
- 离线存储的方案
- 安全性考虑和调试技巧
💡 学习建议:
- 从基本的缓存策略开始
- 注意浏览器兼容性
- 重视安全性考虑
- 做好错误处理
- 实现合适的调试机制
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻