乐趣区

Service Worker

当下 PWA 比较火,而 Service Worker 是实现 PWA 的一项关键技术,今天我们一起了解下关于 Service Worker 的一些基础知识和适用场景。
什么是 Server Worker
我们先来看一下官方文档中对于 Server Worker 的核心定义:
Service workers 本质上充当 Web 应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。
这是一条很准确的定义,但对于不了解 Service Worker 的同学来说可能并不形象,下面我们更形象的理解一下这个概念。我们以去银行取钱为例子,其过程大概如下图:

从上面的图中我们可以看到,银行的钱存在金库中,客户取钱时并不是直接去金库里拿,而是需要通过银行的工作人员,再告知银行工作人员需要多少钱,并出示相应凭证后,由银行工作人员从金库中拿出钱给客户,并从账户中减去相应金额。这么做的原因很容易理解,因为金库是公用的,所有客户的钱都放在里面,我们无法保证每个客户都能只拿走属于自己的钱,并按照实际情况更新金库记录。
我们的应用在请求服务器资源时,其过程也是类似的:

从上面的图可以看到,请求资源的过程中,HTTP 协议充当了取钱过程中的银行工作人员,客户端应用需要的资源在服务器上,但应用却无法直接去服务器获取资源,而是通过 HTTP 协议进行,请求中指定的各种 Header 信息,就是取钱时的凭证。
而 Service Worker 可以理解成,在客户端创建了一个属于自己的金库,先看图:

当我们需要取钱或者获取资源时,可以先从本地的金库中拿,本地金库没有,再通过原来的流程获取。这时我们再回头看文章开始的定义,应该就能够理解了。
Service Worker 与 Cache 的关系
正常情况下,客户端获取一个资源的过程有如下三步:

而关于请求资源的优化,一般也集中在这三步完成:

不发出请求就能够获得资源;
提高服务器查找资源的速度;
减小返回内容的体积;

看完上面的部分我们可以发现,当使用 Service Worker 中已有的资源时,客户端应用获取资源并没有进入 HTTP 请求的流程,也就是说,通过 Service Worker,客户端应用可以在不发出请求的情况下获得资源,这很容易就让我们想到缓存,那么 Service Worker 和我们平时经常提到的强缓存和协商缓存等是什么关系呢?整理了一个图,可以先看下:

从整体上来说,应用获取一个资源的缓存类型分为上图中的四种,分别是 Service Worker、Memory Cache、Disk Cache 和 No Cache。资源查找顺序为从左向右,找到资源则返回,未找到则继续寻找,直至最终获取资源。上面的图中可以清楚的看出 Service Worker 在缓存类型中的位置,也能看到跟平时经常提到的强缓存和协商缓存的关系。
Service Worker 使用逻辑
在了解了 Service Worker 的概念后,我们看下 Servise Worker 的基本使用逻辑,使用它的基础过程是首先注册一个 Woker,这时浏览器会在后台启动一个新的线程,在这个线程启动后,会按照 Service Worker 中的代码逻辑将一些资源缓存下来,缓存完毕后,启动对页面请求的监听,当监听到页面请求资源时可以做出相对应的响应,比如如果资源在 Service Worker 中缓存过了,就可以直接返回资源。
注册
Service Worker 对象保存在 window.navigator 内,首先调用 register 方法进行注册,导入一个 js 文件,文件中是我们的 Service Worker 逻辑,代码如下:
navigator.serviceWorker.register(‘/sw.js’)
.then(function(reg){
console.log(“success”, reg);
}).catch(function(err) {
console.log(“error”, err);
});
需要注意的是 Service Worker 是有作用域的,它的作用域为文件的当前路径,Service Worker 文件只能管理自己作用下的资源,比如 abcde.com/home/sw.js 的作用域为 abcde.com/home/。
激活
注册后的 Service Worker 就能在调试工具中看到了,下面是一个 chrome 调试面板的截图:

画红框的内容是一些比较关键的信息,比如其中表明了 Service Worker 的文件名和路径,以及当前 Service Worker 的状态,Service Worker 的状态分为几种,STOPPED(已停止)、STARTING(正在启动)、RUNNING(正在运行)和 STOPPING(正在停止),比如上面的截图就处于 RUNNING 状态。
只有处于 running 状态的 Service Worker 才能生效,这就需要对注册后的 Service Worker 进行加载和激活,注册完毕后,Service Worker 会自动开始下载,下载后会触发 install 事件,我们可以监听这个事件并进行下载资源的操作,代码如下:
const CACHE_NAME = “demo-a”;
this.addEventListener(“install”, function(event) {
console.log(“install service worker success”);
caches.open(CACHE_NAME);
let cacheResources = [“https://abcde.com/demo.js”];
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
cache.addAll(cacheResources);
})
);
});
经过上面的代码,demo.js 文件就被我们缓存下来了,下载完后 Service Worker 就会执行激活:
this.addEventListener(“active”, function(event) {
console.log(“service worker active success”);
});
此时我们通过开发者工具就能看到一个激活的 Service Worker 了,整体梳理一下大概是下面的过程:

需要注意的是,图中灰色的部分是一个独立的特殊线程,并不是浏览器渲染页面执行 js 的线程,因此使用 Service Worker 的过程中无需担心会影响页面的渲染。
更新
我们注册了 Service Worker 后,还面临着更新的问题,当我们的业务迭代时必然要更新 Service Worker,在我们理解了它的整个注册过程后,理解更新就很简单了,直接上图:

当应用加载时,会下载 Service Worker 文件,这是在浏览器中就会有两个文件,一个是当前正在使用的 Service Worker,一个是新下载的 Service Worker,当新下载的文件下载完毕后,浏览器会对两个文件进行 Diff 操作,如果发现文件没有更新,则会丢弃掉新下载的 Service Worker 文件,如果发现有变化,则会加载新的 Service Worker,但新加载的 Service Worker 会处于 wating 状态,并不会实际发挥作用,只有当整个浏览器中对正在运行的 Service Worker 没有依赖时,才会将运行中的 Service Worker 抛弃,将新的 Servier Worker 置为激活状态。
常见使用场景

用于浏览器缓存,提高加载速度;
实现离线应用,最近 PWA 如此火爆;
实现消息的主动推送,为 web 应用增加一种给力的交互方式;

兼容性
我们了解一些 Service Worker 的基础知识,以及一些比较常见的使用场景,那么目前 Service Worker 的兼容性如何呢,看下图

目前主流的现代浏览器支持度还是不错的,但是到目前为止全系列的 IE 浏览器均不支持 Service Worker,而且有一点感受很明显,在查资料的过程中,看了网上不少的博客,不同的博客上也有当时写博客时的 Service Worker 支持度,可以明显感觉到 Service Worker 的支持程度在快速提升,随着支持度的提升,相信会有越来越多的开发者在项目中使用这项技术。
借助 Service Worker,真正让 PWA 应用变得流行,也许就在不久的将来。

退出移动版