Service Worker学习笔记

136 阅读7分钟

Service worker 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的资源。它还提供入口以推送通知和访问后台同步 API。

  • 基于web worker(一个独立于JavaScript主线程的独立线程,在里面执行需要消耗大量资源的操作不会堵塞主线程)

  • 在web worker的基础上增加了离线缓存的能力

  • 本质上充当Web应用程序(服务器)与浏览器之间的代理服务器(可以拦截全站的请求,并作出相应的动作->由开发者指定的动作)

  • 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)

  • 由事件驱动的,具有生命周期

  • 可以访问cache和indexDB

  • 支持推送

  • 并且可以让开发者自己控制管理缓存的内容以及版本

概念和用法

Service worker 是一个注册在指定源和路径下的事件驱动 worker。它采用 JavaScript 文件的形式,控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。

Service worker 运行在 worker 上下文:因此它无法访问 DOM,相对于驱动应用的主 JavaScript 线程,它运行在其他线程中,所以不会造成阻塞。它被设计为完全异步;因此,同步 XHR 和 Web Storage 不能在 service worker 中使用。

出于安全考量,Service worker 只能由 HTTPS 承载,毕竟修改网络请求的能力暴露给中间人攻击会非常危险,如果允许访问这些强大的 API,此类攻击将会变得很严重。

使用场景

  • 后台数据同步
  • 响应来自其他源的资源请求
  • 集中接收计算成本高的数据更新,比如地理位置和陀螺仪信息,这样多个页面就可以利用同一组数据
  • 在客户端进行 CoffeeScript、LESS、CJS/AMD 等模块编译和依赖管理(用于开发目的)
  • 后台服务钩子
  • 自定义模板用于特定 URL 模式
  • 性能增强,比如预取用户可能需要的资源,比如相册中的后面数张图片

接口

Cache

表示用于 Request / Response 对象对的存储,作为 ServiceWorker 生命周期的一部分被缓存。

CacheStorage

表示 Cache 对象的存储。提供一个所有命名缓存的主目录,ServiceWorker 可以访问并维护名字字符串到 Cache 对象的映射。

Client

表示 service worker client 的作用域。一个 service worker client 可以是浏览器上下文的一个文档,也可以是一个由 active worker 控制的 SharedWorker

Clients

表示一个 Client 对象容器;是访问当前源的活动的 service worker client 的主要途径。

ExtendableEvent

扩展被分发到 ServiceWorkerGlobalScope 的 install 和 activate 事件时序,作为 service worker 生命周期的一部分。这会确保任何功能型事件(如 FetchEvent)不被分发到 ServiceWorker,直到它更新了数据库架构、删除过期缓存项等等以后。

ExtendableMessageEvent

向 service worker 触发的 message 事件的时间对象(当 ServiceWorkerGlobalScope 从另一个上下文收到通道消息),延长了此类事件的生命周期。

FetchEvent

传递给 ServiceWorkerGlobalScope.onfetch 处理函数的参数,FetchEvent 代表一个在 ServiceWorker 的 ServiceWorkerGlobalScope 中分发的请求动作。它包含关于请求和响应的结果信息,并且提供 FetchEvent.respondWith() 方法,这个方法允许我们提供任意的响应返回到控制页面。

InstallEvent

传递给 oninstall 处理函数的参数,InstallEvent 接口代表一个在 ServiceWorker 的 ServiceWorkerGlobalScope 中分发的安装动作,作为 ExtendableEvent 的子事件,它保证诸如 FetchEvent 的功能性事件在安装过程中不会被分发。

NavigationPreloadManager

提供与 service worker 一起管理资源预加载的方法。

Navigator.serviceWorker 和 WorkerNavigator.serviceWorker

返回一个 ServiceWorkerContainer 对象,该对象提供对相关 document 的注册、删除、更新以及与 ServiceWorker 对象通信的访问。

NotificationEvent

传递给 onnotificationclick 处理函数的参数,NotificationEvent 接口代表在 ServiceWorker 的 ServiceWorkerGlobalScope 中分发的单击事件通知。

ServiceWorker

表示一个 service worker。多个浏览的上下文 (例如 page、worker 等等) 都能通过相同的 ServiceWorker 对象相关联。

ServiceWorkerContainer

提供一个在网络生态中把 service worker 作为一个整体的对象,包括辅助注册,反注册以及更新 service worker,并且访问 service worker 的状态以及他们的注册信息。

ServiceWorkerGlobalScope

表示 service worker 的全局执行上下文。

MessageEvent

表示发送到 ServiceWorkerGlobalScope 的信息。

ServiceWorkerRegistration

表示 service worker 的注册。

SyncEvent

SyncEvent 接口代表在 ServiceWorker 的 ServiceWorkerGlobalScope 上分发的同步动作。

SyncManager

提供用于注册和列出同步注册的接口。

WindowClient

表示在浏览器上下文中记录的 service worker 客户端的作用域,被活动的工作者控制。是 Client 对象的特殊类型,包含一些附加的方法和可用的属性。

生命周期

安装

安装阶段是在ServiceWorker注册成功之后,浏览器开始下载ServiceWorker脚本的阶段;

self.addEventListener('install', function (event) { 
    console.log('install'); 
    event.waitUntil( // 这里可以做一些缓存的操作 ); 
});

我们可以通过event.waitUntil来监听它的完成状态,当它完成之后,我们需要调用event.waitUntil的参数,这个参数是一个Promise对象,当这个Promise对象完成之后,浏览器才会进入下一个阶段。

注意:event.waitUntil不要乱用,它会阻塞浏览器的安装,如果你的Promise对象一直没有完成,那么浏览器就会一直处于安装的状态,这样会影响到浏览器的正常使用。

激活

激活阶段是在安装完成之后,浏览器开始激活ServiceWorker的阶段;

self.addEventListener('activate', function (event) { 
    console.log('activate'); 
    event.waitUntil( // 这里可以做一些清理缓存的操作 ); 
});

不同于安装阶段,激活阶段不需要等待event.waitUntil的传递的Permise对象完成,它会立即进入下一个阶段。

如果是第一次加载sw,在安装后,会直接进入activated阶段。
而如果sw进行更新,情况就会显得复杂一些。流程如下:
首先老的sw为A,新的sw版本为B。
B进入install阶段,而A还处于工作状态,所以B进入waiting阶段。
只有等到A被terminated后,B才能正常替换A的工作。

这个terminated的时机有如下几种方式:

  1. 关闭浏览器一段时间;
  2. 手动清除Service Worker;
  3. 在sw安装时直接跳过waiting阶段.

但是永远不要传递一个可能一直处于pending状态的Promise对象,否则会导致ServiceWorker 一直处在某一个状态而无法响应,导致浏览器卡死。

运行

运行阶段是在激活完成之后,ServiceWorker开始运行的阶段;

这个阶段是一个长期存在的过程,我们可以在fetch事件中监听它,它的回调函数会接收到一个event对象;

self.addEventListener('fetch', function (event) { 
    console.log('fetch'); 
});

监听事件

  • install:安装事件,当ServiceWorker安装成功后,就会触发这个事件,这个事件只会触发一次。

  • activate:激活事件,当ServiceWorker激活成功后,就会触发这个事件,这个事件只会触发一次。

  • fetch:网络请求事件,当页面发起网络请求时,就会触发这个事件。

  • push:推送事件,当页面发起推送请求时,就会触发这个事件。

  • sync:同步事件,当页面发起同步请求时,就会触发这个事件。

  • message:消息事件,当页面发起消息请求时,就会触发这个事件。

  • messageerror:消息错误事件,当页面发起消息错误请求时,就会触发这个事件。

  • error:错误事件,当页面发起错误请求时,就会触发这个事件。

缓存

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <script>
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker
          .register('/service-worker.js', { scope: '/' })
          .then((registration) => {
            console.log(
              'ServiceWorker registration successful with scope: ',
              registration.scope
            );
          })
          .catch((error) => {
            console.log('ServiceWorker registration failed: ', error);
          });

        fetch('/getData.json').then((res) => {
          console.log('fetch 成功', res.data);
        });
      }
    </script>
  </body>
</html>

service-worker.js

self.addEventListener('install', e => {
    console.log('install');
    e.waitUntil(
      caches.open('my-cache')
        .then(cache => {
          return cache.addAll(['/', '/index.html', '/index.css', '/getData.json']);
        })
    );
});

self.addEventListener('activate', e => {
    console.log('activate');
    e.waitUntil(
      caches.keys()
        .then(keys => {
            return Promise.all(
                keys.map(key => {
                    if (key !== 'my-cache') {
                        return caches.delete(key);
                    }
                })
            );
        })
    );
});

self.addEventListener('fetch', e => {
    console.log('fetch', e.request.url);
    e.respondWith(
        caches.match(e.request).then(response => {
            console.log('fetch检查缓存是否存在', response);
            return response || fetch(e.request);
        })
    );
});

参考

juejin.cn/post/716589… zhuanlan.zhihu.com/p/115243059 mp.weixin.qq.com/s/3Ep5pJULv…