JavaScript系列(37)-- Service Workers详解

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))
        );
    }
}

最佳实践建议 💡

  1. 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应用提供了强大的离线能力和后台处理能力。通过本文,我们学习了:

  1. Service Worker的基本概念和实现
  2. 高级功能如推送通知和后台同步
  3. 性能优化技巧
  4. 最佳实践和注意事项
  5. 安全性考虑

💡 学习建议:在使用Service Worker时,要特别注意生命周期管理和缓存策略的选择。同时,要考虑兼容性和降级处理,确保在不支持Service Worker的环境下也能正常工作。


如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关推荐
孤客网络科技工作室6 分钟前
不使用 JS 纯 CSS 获取屏幕宽高
开发语言·javascript·css
轩情吖8 分钟前
一文速通stack和queue的理解与使用
开发语言·c++·后端·deque·优先级队列·stack和queue
Agnes_A2022 分钟前
线性回归笔记1-4
开发语言·python
ByteBlossom66630 分钟前
JavaScript语言的正则表达式
开发语言·后端·golang
mikey棒棒棒30 分钟前
基于Redis实现短信验证码登录
java·开发语言·数据库·redis·session
Pandaconda36 分钟前
【新人系列】Python 入门(二十八):常用标准库 - 上
开发语言·经验分享·笔记·后端·python·面试·标准库
nbsaas-boot44 分钟前
Java 在包管理与模块化中的优势:与其他开发语言的比较
java·开发语言
noravinsc1 小时前
vue request 发送formdata
前端·javascript·vue.js
沉默的煎蛋1 小时前
前后端交互过程
java·开发语言·ide·vscode·eclipse·状态模式·交互
Libby博仙1 小时前
VUE3 vite下的axios跨域
前端·javascript·vue.js·前端框架·node.js