Service Works离线应用与Web Worker线程处理

589 阅读6分钟

目录


HTML5 Service Workers是一种在浏览器后台运行的脚本,它独立于网页主线程,允许开发者拦截和控制网络请求,提供离线缓存、推送通知等功能,从而显著提升Web应用的用户体验和可靠性。

Service Workers基础

注册与生命周期:

  • 注册:在网页中使用navigator.serviceWorker.register()方法注册Service Worker脚本。通常在主页面的
  • 生命周期:Service Worker经历安装(install)、激活(activate)、闲置(idle)、接收到事件(event received)和终止(terminate)等状态。开发者通过Service Worker脚本中的事件监听器(如install、activate、fetch等)来处理各个阶段的任务。

创建Service Worker脚本

  • Service Worker脚本位置:通常放置在网站的根目录或子目录下,如/sw.js。确保脚本可通过HTTPS协议访问,因为Service Workers仅在安全上下文中工作。
  • 缓存策略:在Service Worker脚本中定义缓存策略,决定哪些资源应被缓存以及何时更新缓存。
// sw.js

// 定义缓存名称
const CACHE_NAME = 'my-offline-app-v1';

// 要缓存的资源列表
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/images/icon.png',
  // 其他资源...
];

// 安装阶段
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(urlsToCache);
    })
  );
});

// 激活阶段
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (cacheName !== CACHE_NAME) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

缓存与网络请求处理

  • fetch事件:Service Worker监听fetch事件,当浏览器发起网络请求时,可以拦截并返回缓存中的资源,或者在网络请求失败时提供备用资源。
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      // 如果缓存中有请求的资源,则直接返回
      if (cachedResponse) {
        return cachedResponse;
      }

      // 否则,尝试从网络获取
      return fetch(event.request).then((networkResponse) => {
        // 将网络响应副本存入缓存(可选,取决于策略)
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, networkResponse.clone());
        });

        return networkResponse;
      }).catch(() => {
        // 网络请求失败,返回离线页面(或自定义错误页面)
        return caches.match('/offline.html');
      });
    })
  );
});

更新缓存

  • 更新策略:当Service Worker脚本发生变动时,浏览器会自动尝试更新。在install事件中,可以使用新的缓存名称来创建新的缓存,同时在activate事件中清理旧的缓存。

  • 主动触发更新:在网页中可以通过定期检查Service Worker脚本的更新(如使用navigator.serviceWorker.controller?.registration.update()),或者在Service Worker中监听message事件,由网页发送更新指令来主动触发更新。

其他功能

推送通知:通过Service Workerpushnotificationclick事件,可以实现Web Push通知功能,即使用户不在应用内也能接收到通知。

后台同步:利用sync事件,可在网络恢复时执行延迟的后台任务,如离线时保存的数据同步到服务器。

测试与调试

开发者工具:使用浏览器的开发者工具(如Chrome DevTools的"Application"面板),可以查看已注册的Service Workers、缓存内容、推送订阅状态等,以及模拟离线状态进行测试。

日志输出:在Service Worker脚本中使用console.log()记录日志,帮助调试。

Service Works应用示例

sw.js(Service Worker脚本)

// 定义缓存名称
const CACHE_NAME = 'my-offline-app-v1';

// 要缓存的资源列表
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/images/icon.png',
  // 其他资源...
];

// 安装阶段
self.addEventListener('install', (event) => {
  console.log('[Service Worker] Installing...');
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('[Service Worker] Caching app shell');
      return cache.addAll(urlsToCache);
    })
  );
});

// 激活阶段
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) {
            console.log(`[Service Worker] Removing old cache ${cacheName}`);
            return caches.delete(cacheName);
          }
        })
      );
    })
  );

  console.log('[Service Worker] Activated');
});

// 缓存与网络请求处理
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      if (cachedResponse) {
        console.log(`[Service Worker] Serving from cache: ${event.request.url}`);
        return cachedResponse;
      }

      console.log(`[Service Worker] Fetching: ${event.request.url}`);
      return fetch(event.request).then((networkResponse) => {
        // 可选:缓存网络响应
        // caches.open(CACHE_NAME).then((cache) => {
        //   cache.put(event.request, networkResponse.clone());
        // });

        return networkResponse;
      }, () => {
        // 网络请求失败,返回离线页面(或自定义错误页面)
        return caches.match('/offline.html');
      });
    })
  );
});

index.html(主页面)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Service Worker Demo</title>
  <link rel="stylesheet" href="/styles.css">
  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', function() {
        navigator.serviceWorker.register('/sw.js')
          .then((registration) => {
            console.log('[App] Service Worker registered:', registration);
          })
          .catch((error) => {
            console.error('[App] Service Worker registration failed:', error);
          });
      });
    } else {
      console.log('[App] Service Worker is not supported');
    }
  </script>
</head>
<body>
  <h1>Welcome to the Service Worker Demo</h1>
  <p>This page and its resources are cached for offline use.</p>
  <img src="/images/icon.png" alt="Icon">
  <script src="/app.js"></script>
</body>
</html>
  1. 在sw.js中,定义了缓存名称(CACHE_NAME)和要缓存的资源列表(urlsToCache)。在install事件处理器中,打开缓存并添加指定资源。在activate事件处理器中,删除旧的缓存。
  2. 在fetch事件处理器中,优先返回缓存中的资源,否则尝试网络请求。网络请求失败时,返回离线页面(这里假设存在/offline.html)。
  3. 在index.html中,检查浏览器是否支持Service Worker。支持的话,在页面加载完成后注册sw.js。在控制台可以看到Service Worker的注册状态和日志输出。

Web Worker的基本使用

Web Worker是HTML5提供的一个API,允许在浏览器环境中创建后台线程,从而实现Web应用程序的多线程处理。由于JavaScript在浏览器端默认是单线程执行的,对于长时间运行的计算、大数据处理、密集型计算等任务,如果不进行适当的分离,可能会导致用户界面卡顿或无响应。Web Worker通过创建独立的工作线程,可以在不影响主线程(即UI线程)的情况下执行这些任务,提升用户体验。

  1. 创建Worker: 使用new Worker(url)创建一个Worker实例,其中url是包含Worker脚本的URL。

  2. 通信:

  • 主线程向Worker发送消息:使用worker.postMessage(data)方法,data可以是任意序列化后的JavaScript值。
  • Worker向主线程发送消息:在Worker脚本中使用self.postMessage(data)。
  • 接收消息:
    • 主线程:通过监听worker.onmessage事件,处理来自Worker的消息。
    • Worker:在Worker脚本中使用self.addEventListener('message', handler)来添加消息处理器。
  1. 生命周期管理:
    • 启动:创建Worker实例即启动线程。
    • 终止:调用worker.terminate()方法停止Worker线程。

Web Worker应用示例

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Web Worker Example</title>
</head>
<body>
  <button onclick="startWorker()">Start Worker</button>
  <button onclick="stopWorker()">Stop Worker</button>
  <div id="result">Result: Loading...</div>

  <script>
    let myWorker;

    function startWorker() {
      if (!window.Worker) {
        alert("Your browser does not support Web Workers.");
        return;
      }

      myWorker = new Worker('worker.js');

      myWorker.onmessage = function(e) {
        document.getElementById('result').textContent = 'Result: ' + e.data;
      };

      myWorker.postMessage([1, 2, 3, 4, 5, ...Array(1000000).fill(0).map((_, i) => i + 6)]);
    }

    function stopWorker() {
      if (myWorker) {
        myWorker.terminate();
        myWorker = null;
        document.getElementById('result').textContent = 'Result: Worker stopped';
      }
    }
  </script>
</body>
</html>

Worker脚本(worker.js):

// This code runs in a separate thread

self.addEventListener('message', function(e) {
  const numbers = e.data;
  let sum = 0;

  for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }

  self.postMessage(sum);
}, false);

在这个示例中:

  • 当用户点击“Start Worker”按钮时,创建一个新的Web Worker实例,指向worker.js脚本。
  • 向Worker发送一个包含大量数字的大数组。
  • Worker接收到消息后,计算数组的累加和。
  • 计算完成后,Worker通过postMessage将结果发送回主线程。
  • 主线程接收到消息后,更新页面显示结果。
  • 用户点击“Stop Worker”按钮时,终止Worker线程。