了解 Service Worker

1 篇文章 0 订阅
ServiceWorker是一种增强版WebWorker,用于实现离线缓存和后台同步,常见于PWA中。它有独立的上下文,不阻塞网页运行,需在HTTPS环境。ServiceWorker生命周期包括解析、安装、已安装、激活中、已激活和废弃六个状态。通过navigator.serviceWorker注册和管理,可用于监听fetch事件,实现网络请求拦截。
摘要由CSDN通过智能技术生成

什么是 Service Worker

Service Worker(后面简称SW) 是 Web Worker 的一种增强版,在后台独立于网页运行,和普通脚本拥有不同的运行容器,其中运行的代码不会被普通的JS阻塞,同时也不会阻塞其它页面的JS文件中的代码。常见于 PWA 中,用于拦截全局的 fetch 事件,实现离线缓存、后台同步等操作。

SW主要包含以下特性:

  • 有自己的上下文,独立于当前网页进程
  • 一旦安装,永远存在,需要手动卸载
  • 事件型驱动,使用时自动唤醒,不使用时自动休眠
  • 必须允许在 https 或者 localhost(127.0.0.1)下,也不能运行在浏览器的无痕模式下

浏览器支持情况

当前主流浏览器都已经支持,实时查看

在这里插入图片描述

相关接口介绍

SW API 主要包含四个接口:

  • ServiceWorkerContainer:用于注册、注销 SW 线程的容器
  • ServiceWorkerRegistration:对 SW 注册实例的引用,可用于注册同步消息、推送消息、通知等
  • ServiceWorker:提供了对 SW 线程的引用,可用于获取线程信息和向线程发送消息。状态包含以下几种:
    • parsed
    • installing
    • installed(Waiting)
    • activating
    • activated
    • redundant
  • ServiceWorkerGlobalScope:代表一个SW的全局执行上下文,使用 self 标识。包含很多属性和事件,后续很多事件的操作都是对这个接口的操作。

ServiceWorker生命周期

为了方便理解,可以从两个角度来看待SW的生命周期:脚本自身的生命周期 和 SW线程的生命周期。

脚本的生命周期

我们在上面介绍相关接口的时候,提到 ServiceWorker 包含6个状态,这其实就是脚本从注册到销毁的整个生命周期。

  • 解析(Parsed):当 ServiceWorkerContainer.register 执行成功后,注册的 Service Worker 文件解析完成
  • 安装(Installing):注册完成后,SW线程会转入 installing 状态,每个SW 知会调用一次安装事件。ServiceWorkerGlobalScope.oninstall 事件会被触发,可以再这个事件中做一些静态资源的缓存等操作。在这个阶段,可以通过 event.waitUtil() 的 Promise 来告诉浏览器什么时候安装完成。
  • 已安装(Installed):ServiceWorkerGlobalScope.oninstall 中处理完成后,状态即为 installed。此时新的 SW 线程处于等待状态,可以手动调用 self.skipWaiting 或者重新打开网页来进行激活。
  • 激活中(activating):激活中状态下会触发 ServiceWorkerGlobalScope.onactivate 事件,可以再这个事件里处理一些旧版本的资源删除操作。在此状态下手动调用 self.clients.claim(),相关页面会立刻被新的 SW 线程控制,并触发 ServiceWorkerContainer.oncontrollerchange 事件
  • 已激活(activated):ServiceWorkerGlobalScope.onactivate 事件中的处理逻辑完成后,更新状态为已激活
  • 废弃(redundant):安装失败、激活失败会导致当前注册的 SW 线程废弃。
线程的生命周期

伴随着脚本的注册实例化,会实例化出一个SW线程,这个线程是属于事件驱动的,空闲时关闭线程进行休眠,fetch/sync/push等事件触发时开启线程进行工作。

在这里插入图片描述

使用

举个栗子

navigator.serviceWorker // ServiceWorkerContainer 对象: window环境下用于注册、注销 Service Worker 线程的容器
        .register('sw.js', {
          scope: '.',
          updateViaCache: 'none'
        })
        .then((swReg) => {
          console.log('sw.js 注册成功');
          swReg.onupdatefound = () => {
            const workerInstalling = swReg.installing; // 返回 ServiceWorker 对象, 对 SW 线程的引用,可用于获取线程信息和向线程发送消息。
            console.log(workerInstalling);
            if (!workerInstalling) {
              return;
            }
            // 监听状态
            workerInstalling.onstatechange = () => {
              console.log(`====> worker 状态 ${workerInstalling.state}`);
            };
          };
        })
        .catch((e) => {
          console.log('sw.js 注册失败', e);
        });

      document.getElementById('unReg').onclick = () => {
        navigator.serviceWorker
          .getRegistration()
          .then((swReg) => {
            if (!swReg) {
              return Promise.resolve(true);
            }
            return swReg.unregister();
          })
          .then((ret) => {
            if (ret) {
              console.log('卸载成功');
            }
          });
      };
const CACHE_NAME = 'pwa'; // 定义缓存名称

// SW安装成功后开始缓存所需的资源
self.addEventListener('install', (e) => {
  console.log('监听 install 事件:', e);
  // self.skipWaiting(); // 跳过等待
  e.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      cache.addAll([
        // 在 SW 安装时缓存相关资源
        'images/face.jpg',
        'custom404.html',
        '/',
        'index.html'
      ]);
    })
  );
});

// 激活阶段,说明上一个SW已经失效,可以在这里删除上个SW的缓存
self.addEventListener('activate', (e) => {
  console.log('监听 active 事件:', e);
  clients.claim(); // 立即受控
});

// 拦截代理所有指定的请求,并进行对应的操作
self.addEventListener('fetch', (e) => {
  console.log('监听 fetch 事件:', e);
  return e.respondWith(
    fetch(e.request)
      .then((res) => {
        if (e.request.mode == 'navigate' && res.status == 404) {
          return fetch('custom404.html');
        }
        return res;
      })
      .catch(() => {
        return caches.open(CACHE_NAME).then((cache) => {
          return cache.match(e.request).then((res) => {
            if (res) {
              return res;
            }
            return cache.match('custom404.html');
          });
        });
      })
  );
});

调试

  • 可以通过 Application 的页签查看注册的 SW
    在这里插入图片描述

  • 可以通过NetWork查看部分文件 from Service Worker
    在这里插入图片描述

  • 通过 Cache Storage 查看缓存
    在这里插入图片描述

  • 断点调试,注意非 main 线程
    在这里插入图片描述

常见问题

页面受控指什么?

指某个页面被注册的SW线程控制,即监听这个页面所有的动作,比如fetch、push等事件

注册 SW 时传入的 Scope 怎么理解?

即SW 注册的作用域,默认是与脚本网址相对的 ./。SW会监听和代理所有作用域下的请求。

首次注册激活的 SW 为什么需要再次加载来生效?

主要是首次注册之后不会立即受控这个网页,需要二次加载。
此时可以通过调用clients.claim()让SW立马获得该网页的控制权。

// sw.js
self.addEventListener('activate', event => {
   event.waitUntil(clients.claim());
   console.log('Now ready to handle fetches!');
});

参考

一个需求引发的 Service Worker 浅理解
网易云课堂 Service Worker 运用与实践

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值