JavaScript Service Workers详解 🔄
今天,让我们深入了解Service Workers,这是一种强大的Web技术,能够实现离线缓存、推送通知和后台同步等功能。
Service Workers基础概念 🌟
💡 小知识:Service Worker是一种运行在浏览器后台的脚本,它可以拦截和处理网络请求,实现资源缓存、推送通知等功能,是实现Progressive Web Apps (PWA)的核心技术之一。
基本实现 📊
javascript
// 1. Service Worker注册
class ServiceWorkerManager {
constructor(options = {}) {
this.options = {
scope: '/',
...options
};
}
async register(scriptUrl) {
try {
if (!('serviceWorker' in navigator)) {
throw new Error('Service Worker not supported');
}
const registration = await navigator.serviceWorker.register(
scriptUrl,
{ scope: this.options.scope }
);
console.log('Service Worker registered:', registration.scope);
return registration;
} catch (error) {
console.error('Service Worker registration failed:', error);
throw error;
}
}
async unregister() {
const registration = await navigator.serviceWorker.getRegistration();
if (registration) {
await registration.unregister();
console.log('Service Worker unregistered');
}
}
}
// 2. 缓存管理器
class CacheManager {
constructor(cacheName) {
this.cacheName = cacheName;
}
async addToCache(request, response) {
const cache = await caches.open(this.cacheName);
await cache.put(request, response);
}
async getFromCache(request) {
const cache = await caches.open(this.cacheName);
return cache.match(request);
}
async deleteFromCache(request) {
const cache = await caches.open(this.cacheName);
await cache.delete(request);
}
async clearCache() {
await caches.delete(this.cacheName);
}
}
// 3. 网络请求拦截器
class RequestInterceptor {
constructor(strategies = {}) {
this.strategies = {
'cache-first': this.cacheFirst.bind(this),
'network-first': this.networkFirst.bind(this),
'cache-only': this.cacheOnly.bind(this),
'network-only': this.networkOnly.bind(this),
'stale-while-revalidate': this.staleWhileRevalidate.bind(this),
...strategies
};
}
async cacheFirst(request, cacheName) {
const cache = await caches.open(cacheName);
const cached = await cache.match(request);
if (cached) {
return cached;
}
const response = await fetch(request);
await cache.put(request, response.clone());
return response;
}
async networkFirst(request, cacheName) {
try {
const response = await fetch(request);
const cache = await caches.open(cacheName);
await cache.put(request, response.clone());
return response;
} catch (error) {
const cached = await caches.match(request);
if (cached) {
return cached;
}
throw error;
}
}
async cacheOnly(request) {
const cached = await caches.match(request);
if (!cached) {
throw new Error('No cached response found');
}
return cached;
}
async networkOnly(request) {
return fetch(request);
}
async staleWhileRevalidate(request, cacheName) {
const cache = await caches.open(cacheName);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then(response => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
}
高级功能实现 🚀
javascript
// 1. 推送通知管理器
class NotificationManager {
constructor() {
this.permission = Notification.permission;
}
async requestPermission() {
try {
const permission = await Notification.requestPermission();
this.permission = permission;
return permission;
} catch (error) {
console.error('Failed to request notification permission:', error);
throw error;
}
}
async subscribe(registration) {
try {
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: this.urlBase64ToUint8Array(
'YOUR_PUBLIC_VAPID_KEY'
)
});
return subscription;
} catch (error) {
console.error('Failed to subscribe to push:', error);
throw error;
}
}
async showNotification(title, options = {}) {
if (this.permission !== 'granted') {
throw new Error('Notification permission not granted');
}
const registration = await navigator.serviceWorker.getRegistration();
await registration.showNotification(title, {
icon: '/icon.png',
badge: '/badge.png',
...options
});
}
urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
}
// 2. 后台同步管理器
class BackgroundSyncManager {
constructor(registration) {
this.registration = registration;
}
async register(tag) {
try {
await this.registration.sync.register(tag);
console.log(`Background sync registered: ${tag}`);
} catch (error) {
console.error('Background sync registration failed:', error);
throw error;
}
}
async getTags() {
const tags = await this.registration.sync.getTags();
return tags;
}
}
// 3. 离线数据管理器
class OfflineDataManager {
constructor(dbName = 'offlineDB') {
this.dbName = dbName;
this.db = null;
}
async initDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('offlineData')) {
db.createObjectStore('offlineData', { keyPath: 'id' });
}
};
});
}
async saveData(data) {
if (!this.db) await this.initDB();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(
['offlineData'],
'readwrite'
);
const store = transaction.objectStore('offlineData');
const request = store.put(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async getData(id) {
if (!this.db) await this.initDB();
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(['offlineData'], 'readonly');
const store = transaction.objectStore('offlineData');
const request = store.get(id);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
性能优化技巧 ⚡
javascript
// 1. 预缓存策略
class PrecacheManager {
constructor(cacheVersion) {
this.cacheVersion = cacheVersion;
this.precacheList = new Set();
}
addResources(resources) {
resources.forEach(resource => this.precacheList.add(resource));
}
async precache() {
const cache = await caches.open(this.cacheVersion);
const existingKeys = await cache.keys();
const existingUrls = new Set(
existingKeys.map(request => request.url)
);
const newResources = Array.from(this.precacheList)
.filter(url => !existingUrls.has(url));
await Promise.all(
newResources.map(async url => {
try {
const response = await fetch(url);
await cache.put(url, response);
} catch (error) {
console.error(`Failed to precache ${url}:`, error);
}
})
);
}
async cleanup() {
const cache = await caches.open(this.cacheVersion);
const keys = await cache.keys();
await Promise.all(
keys.map(async request => {
if (!this.precacheList.has(request.url)) {
await cache.delete(request);
}
})
);
}
}
// 2. 请求优化器
class RequestOptimizer {
constructor() {
this.requestQueue = new Map();
this.batchSize = 5;
this.batchDelay = 100;
}
async optimizeRequest(request) {
const key = this.getRequestKey(request);
if (this.requestQueue.has(key)) {
return this.requestQueue.get(key);
}
const promise = this.processRequest(request);
this.requestQueue.set(key, promise);
try {
const response = await promise;
return response;
} finally {
this.requestQueue.delete(key);
}
}
getRequestKey(request) {
return `${request.method}:${request.url}`;
}
async processRequest(request) {
// 实现请求处理逻辑
return fetch(request);
}
}
// 3. 缓存优化器
class CacheOptimizer {
constructor(maxSize = 100) {
this.maxSize = maxSize;
this.cacheUsage = new Map();
}
async optimizeCache(cacheName) {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
if (keys.length <= this.maxSize) return;
// 更新使用频率
keys.forEach(key => {
const count = this.cacheUsage.get(key.url) || 0;
this.cacheUsage.set(key.url, count + 1);
});
// 按使用频率排序
const sortedUrls = Array.from(this.cacheUsage.entries())
.sort((a, b) => b[1] - a[1])
.map(([url]) => url);
// 删除最少使用的缓存
const urlsToDelete = sortedUrls.slice(this.maxSize);
await Promise.all(
urlsToDelete.map(url => cache.delete(url))
);
}
}
最佳实践建议 💡
- Service Worker生命周期管理
javascript
// 1. 生命周期处理器
class LifecycleHandler {
constructor() {
this.version = '1.0.0';
this.handlers = new Map();
}
onInstall(callback) {
this.handlers.set('install', callback);
}
onActivate(callback) {
this.handlers.set('activate', callback);
}
onFetch(callback) {
this.handlers.set('fetch', callback);
}
async handleInstall(event) {
const handler = this.handlers.get('install');
if (handler) {
event.waitUntil(handler(event));
}
}
async handleActivate(event) {
const handler = this.handlers.get('activate');
if (handler) {
event.waitUntil(handler(event));
}
}
handleFetch(event) {
const handler = this.handlers.get('fetch');
if (handler) {
event.respondWith(handler(event));
}
}
}
// 2. 错误处理
class ErrorHandler {
static async handle(error, context) {
console.error(`Error in ${context}:`, error);
// 发送错误报告
try {
await fetch('/error-report', {
method: 'POST',
body: JSON.stringify({
error: error.message,
context,
timestamp: Date.now()
})
});
} catch (e) {
console.error('Failed to send error report:', e);
}
// 返回降级响应
return new Response('Service Worker Error', {
status: 500,
headers: { 'Content-Type': 'text/plain' }
});
}
}
// 3. 安全策略
class SecurityPolicy {
constructor() {
this.allowedOrigins = new Set();
this.allowedPaths = new Set();
}
addAllowedOrigin(origin) {
this.allowedOrigins.add(origin);
}
addAllowedPath(path) {
this.allowedPaths.add(path);
}
isRequestAllowed(request) {
const url = new URL(request.url);
return this.allowedOrigins.has(url.origin) ||
this.allowedPaths.has(url.pathname);
}
}
结语 📝
Service Workers为Web应用提供了强大的离线能力和后台处理能力。通过本文,我们学习了:
- Service Worker的基本概念和实现
- 高级功能如推送通知和后台同步
- 性能优化技巧
- 最佳实践和注意事项
- 安全性考虑
💡 学习建议:在使用Service Worker时,要特别注意生命周期管理和缓存策略的选择。同时,要考虑兼容性和降级处理,确保在不支持Service Worker的环境下也能正常工作。
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻