当前位置: 首页 > article >正文

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

  1. Service Worker 的生命周期和注册过程
  2. 各种缓存策略的实现
  3. 推送通知的处理
  4. 后台同步的实现
  5. 离线存储的方案
  6. 安全性考虑和调试技巧

💡 学习建议:

  1. 从基本的缓存策略开始
  2. 注意浏览器兼容性
  3. 重视安全性考虑
  4. 做好错误处理
  5. 实现合适的调试机制

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

终身学习,共同成长。

咱们下一期见

💻


http://www.kler.cn/a/554644.html

相关文章:

  • RAGFLOW使用flask转发的open ai接口
  • BFS 解决 FloodFill 算法(典型算法思想)—— OJ例题算法解析思路
  • 神经网络八股(2)
  • Unity 位图字体
  • 3.1 actor基本框架(c#的Akka.Actor模式)
  • 约束性委派攻击和非约束性委派攻击
  • Vue 3 工程化:从理论到实践 (上篇)
  • DeepSeek在企业中的有那些具体应用?
  • 易基因: ChIP-seq+DRIP-seq揭示AMPK通过调控H3K4me3沉积和R-loop形成以维持基因组稳定性和生殖细胞完整性|NAR
  • jvm中各个参数的理解
  • ROS 2机器人开发--第一个节点
  • HTTP SSE 实现
  • 【清华大学】DeepSeek从入门到精通完整版pdf下载
  • Could not initialize class io.netty.util.internal.Platfor...
  • nginx配置ssl
  • 《Spring实战》(第6版) 第3章 使用数据
  • 前端PDF转图片技术调研实战指南:从踩坑到高可用方案的深度解析
  • 基于Java爬虫获取1688商品分类信息(cat_get接口)的实现指南
  • 常用网络工具分析(ping,tcpdump等)
  • 如何平衡网络服务的成本、质量和可扩展性?