WorkBox 之底层逻辑Service Worker(一)

简介: WorkBox 之底层逻辑Service Worker(一)

人生就像钟摆,晃动在痛苦和无聊之间,其动力便是欲望

大家好,我是柒八九

前言

在前几天师夷长技以制夷:跟着PS学前端技术文件中,我们提到了WorkBox,然后自己也对这块很感兴趣,所以就利用业余时间进行相关资源的查询学习和实践。在学习过程中发现,想要弄明白WorkBox,有一点很关键,我们需要搞懂Service Worker

而在之前的

  1. Web性能优化之Worker线程(上)
  2. Web性能优化之Worker线程(下)

其实已经写过相关的文章,但是由于当时的技术所限,其中的内容只是单纯的从实现逻辑上,也就是API层面做了一次不完整归纳总结。总体从Worker层面的继承关系和简单使用方面出发。

image.png

而,今天我们再次对Service Worker做一次深度的剖析。当然,其中API的部分大家可以翻看之前的文章。下文中不再赘述。

好了,天不早了,干点正事哇。


我们能所学到的知识点

  1. 前置知识点
  2. service workers 能为我们带来什么
  3. Service worker 的生命周期
  4. Service worker 缓存策略
  5. Service Worker 预缓存的陷阱
  6. 改进Service Worker开发体验

1. 前置知识点

前置知识点,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。如果大家对这些概念熟悉,可以直接忽略


同时,由于阅读我文章的群体有很多,所以有些知识点可能我视之若珍宝,尔视只如草芥,弃之如敝履。以下知识点,请酌情使用

如何查看Service Worker

要查看正在运行的Service workers列表,我们可以在Chrome/Chromium中地址栏中输入chrome://serviceworker-internals/

image.png

chrome://xx 包含了很多内置的功能,这块也是有很大的说道的。后期,会单独有一个专题来讲。(已经在筹划准备中....)

Cache API

Cache API为缓存的 Request / Response 对象对提供存储机制。例如,作为ServiceWorker 生命周期的一部分

Cache APIworkers 一样,是暴露在 window 作用域下的。尽管它被定义在 service worker 的标准中,但是它不必一定要配合 service worker 使用。

一个域可以有多个命名 Cache 对象。我们需要在脚本 (例如,在 ServiceWorker 中) 中处理缓存更新的方式。

  • 除非明确地更新缓存,否则缓存将不会被更新;
  • 除非删除,否则缓存数据不会过期
  • 使用 CacheStorage.open(cacheName) 打开一个 Cache 对象,再使用 Cache 对象的方法去处理缓存。
  • 需要定期地清理缓存条目,因为每个浏览器都硬性限制了一个域下缓存数据的大小。
  • 缓存配额使用估算值,可以使用 StorageEstimate API 获得。
  • 浏览器尽其所能去管理磁盘空间,但它有可能删除一个域下的缓存数据。
  • 浏览器要么自动删除特定域的全部缓存,要么全部保留。

一些围绕service worker缓存的重要 API 方法包括:

  • CacheStorage.open用于创建新的 Cache 实例。
  • Cache.addCache.put用于将网络响应存储在service worker缓存中。
  • Cache.match用于查找 Cache 实例中的缓存响应。
  • Cache.delete用于从 Cache 实例中删除缓存响应。
  • .....

Cache.put, Cache.addCache.addAll只能在GET请求下使用。

更多详情可以参考MDN-Cache

Cache API 与 HTTP 缓存的区别

如果我们以前没有使用过Cache接口,可能会认为它与 HTTP 缓存相同,或者至少与 HTTP 缓存相关。但实际情况并非如此。

  • Cache接口是一个完全独立于HTTP 缓存的缓存机制
  • 用于影响HTTP缓存的任何Cache-Control配置对存储在Cache接口中的资源没有影响。

可以将浏览器缓存看作是分层的

  • HTTP缓存是一个由键-值对驱动低级缓存,其中的指令在HTTP Header中表示。
  • Cache接口是由JavaScript API 驱动高级缓存。这比使用相对简单的HTTP键-值对具有更大的灵活性。

2. service workers 能为我们带来什么

Service workersJavaScript层面的 API,充当 Web 浏览器和 Web 服务器之间的代理。它们的目标是通过提供离线访问以及提升页面性能来提高可靠性。

渐进增强,类似应用程序生命周期

Service workers是对现有网站的增强。这意味着如果使用Service workers的网站的用户使用不支持Service workers的浏览器访问网站,基本功能不会受到破坏。它是向下兼容的。

Service workers通过类似于桌面应用程序的生命周期逐渐增强网站。想象一下当从应用商城安装APP时会发生流程:

  1. 发出下载APP的请求。
  2. APP下载并安装。
  3. APP准备好使用并可以启动。
  4. APP进行新版本的更新。

Service worker也采用类似的生命周期,但采用渐进增强的方法。

  1. 在首次访问安装了新Service worker的网页时,初始访问提供网站的基本功能,同时Service worker开始下载
  2. 安装激活Service worker后,它将控制页面以提供更高的可靠性速度

采用 JavaScript 驱动的 Cache API

Service worker技术中不可或缺的一部分是Cache API,这是一种完全独立于 HTTP 缓存的缓存机制Cache API可以在Service worker作用域内和主线程作用域内访问。该特性为用户操作与 Cache 实例的交互提供了许多可能性。

  • HTTP缓存是通过HTTP Header中指定的缓存指令来影响的
  • Cache API可以通过 JavaScript 进行编程

这意味着可以根据网站的特有的逻辑来缓存网络请求的响应。例如:

  1. 首次请求静态资源时将其存储在缓存中,然后在后续请求中从缓存中获取
  2. 页面结构存储在缓存中,但在离线情况下从缓存中获取。
  3. 对于一些非紧急的资源,先从缓存中获取,然后在后台中通过网络再更新它。下次再获取该资源时候,就认为是最新的
  4. 网络采用流式传输处理部分内容,并与缓存中的应用程序拦截层组合以改善感知性能。

这些都是缓存策略的应用方向。缓存策略使离线体验成为可能,并通过绕过 HTTP 缓存触发的高延迟重新验证检查提供更好的性能

异步和事件驱动的 API

网络上传输数据本质上是异步的。请求资产、服务器响应请求以及下载响应都需要时间。所涉及的时间是多样且不确定的。Service workers通过事件驱动的 API 来适应这种异步性,使用回调处理事件,例如:

  1. Service worker正在安装时。
  2. Service worker正在激活时。
  3. Service worker检测到网络请求时。

都可以使用addEventListener API 注册事件。所有这些事件都可以与Cache API进行交互。特别是在网络请求是离散的,运行回调的能力对于提供所期望的可靠性和速度至关重要。

JavaScript中进行异步工作涉及使用Promises。因为Promises也支持asyncawait,这些JavaScript特性也可用于简化Service worker代码,从而提供更好的开发者体验。

预缓存和运行时缓存

Service workerCache实例之间的交互涉及两个不同的缓存概念:

  1. 预缓存(Precaching caching)
  2. 运行时缓存(Runtime caching)

预缓存是需要提前缓存资源的过程,通常在Service worker安装期间进行。通过预缓存关键的静态资产和离线访问所需的材料可以被下载并存储在 Cache 实例中。这种类型的缓存还可以提高需要预缓存资源的后续页面的页面速度。

运行时缓存是指在运行时从网络请求资源时应用缓存策略。这种类型的缓存非常有用,因为它保证了用户已经访问过的页面和资源的离线访问。

当在Service worker中使用这些方法时,可以为用户体验提供巨大的好处,并为普通的网页提供类似应用程序的行为。

与主线程隔离

Service workersWeb workers类似,它们的所有工作都在自己的线程上进行。这意味着Service workers的任务不会与主线程上的其他任务竞争。

我们就以Web Worker为例子,做一个简单的演示 在JavaScript中创建Web Worker并不是一项复杂的任务。

  1. 创建一个新的JavaScript文件,其中包含我们希望在工作线程中运行的代码。此文件不应包含对DOM的任何引用,因为它将无法访问DOM。
  2. 在我们的主JavaScript文件中,使用Worker构造函数创建一个新的Worker对象。此构造函数接受一个参数,即我们在第1步中创建的JavaScript文件的URL
const worker = new Worker('worker.js');
  1. Worker对象添加事件侦听器,以处理主线程和工作线程之间发送的消息。onmessage事件处理程序用于处理从工作线程发送的消息,而postMessage方法用于向工作线程发送消息。
worker.onmessage = function(event) {
  console.log('Worker said: ' + event.data);
};
worker.postMessage('Hello, worker!');
  1. 在我们的工作线程JavaScript文件中,添加一个事件侦听器,以处理从主线程发送的消息,使用self对象onmessage属性。我们可以使用event.data属性访问消息中发送的数据。
self.onmessage = function(event) {
  console.log('Main thread said: ' + event.data);
  self.postMessage('Hello, main thread!');
};

现在让我们运行Web应用程序并测试Worker。我们应该在控制台中看到打印的消息,指示主线程和工作线程之间已发送和接收消息。

image.png


3. Service worker 的生命周期

定义术语

在深入了解service worker的生命周期之前,我们先来了解一下与生命周期运作相关的术语黑话

控制和作用域

了解service worker运作方式的关键在于理解控制control)。

  • service worker控制的页面允许service worker代表该页面进行拦截网络请求。
  • 在给定的作用域scope)内,service worker能够为页面执行处理资源的相关工作。

作用域

一个service worker作用域由其在 Web 服务器上的位置确定。如果一个service worker在位于/A/index.html的页面上运行,并且位于/A/sw.js上,那么该service worker作用域就是/A/

  1. 打开https://service-worker-scope-viewer.glitch.me/subdir/index.html。将显示一条消息,说明没有service worker正在控制该页面。但是,该页面从https://service-worker-scope-viewer.glitch.me/subdir/sw.js注册了一个service worker
  2. 重新加载页面。因为service worker已经注册并处于活动状态,它正在控制页面。将显示一个包含service worker作用域、当前状态和其 URL 的表单。
  3. 现在打开https://service-worker-scope-viewer.glitch.me/index.html。尽管在此origin上注册了一个service worker,但仍然会显示一条消息,说明没有当前的service worker。这是因为此页面不在已注册service worker的作用域内。

作用域限制了service worker控制的页面。在上面的例子中,这意味着从/subdir/sw.js加载的service worker只能控制位于/subdir/或其子页面中

控制页面的service worker仍然可以拦截任何网络请求,包括跨域资源的请求。作用域限制了由service worker控制的页面。

上述是默认情况下作用域工作的方式,但可以通过设置Service-Worker-Allowed响应头,以及通过向register方法传递作用域选项来进行覆盖。

除非有很好的理由将service worker的作用域限制为origin的子集,否则应从 Web 服务器的根目录加载service worker,以便其作用域尽可能广泛,不必担心Service-Worker-Allowed头部。

客户端

当说一个service worker正在控制一个页面时,实际上是在控制一个客户端。客户端是指URL位于该service worker作用域内的任何打开的页面。具体来说,这些是WindowClient的实例。

image.png


3.1 Service worker 在初始化时的生命周期

为了使service worker能够控制页面,首先必须将其部署。

让我们看看一个没有service worker的网站到部署全新service worker时,中间发生了啥?

1. 注册(Registration)

注册service worker生命周期的初始步骤

<script>
  // 直到页面完全加载后再注册service worker
  window.addEventListener("load", () => {
    // 检查service worker是否可用
    if ("serviceWorker" in navigator) {
      navigator.serviceWorker
        .register("/sw.js")
        .then(() => {
          console.log("Service worker 注册成功!");
        })
        .catch((error) => {
          console.warn("注册service worker时发生错误:");
          console.warn(error);
        });
    }
  });
</script>

此代码在主线程上运行,并执行以下操作:

  • 因为用户首次访问网站时没有注册service worker,所以等待页面完全加载后再注册一个。这样可以避免在service worker预缓存任何内容时出现带宽争用
  • 尽管service worker得到了广泛支持,但进行特性检查可以避免在不支持它的浏览器中出现错误。
  • 当页面完全加载后,如果支持service worker,则注册/sw.js

还有一些关键要点:

  • Service worker仅在HTTPSlocalhost上可用。
  • 如果service worker的内容包含语法错误,注册会失败,并丢弃service worker
  • service worker在一个作用域内运行。在这里,作用域是整个origin,因为它是从根目录加载的。
  • 当注册开始时,service worker的状态被设置为installing

一旦注册完成,安装就开始了。

2. 安装(Installation)

service worker在注册后触发其install事件。install只会在每个service worker中调用一次,直到它被更新才会再次触发。可以使用addEventListenerworker作用域内注册install事件的回调:

// /sw.js
self.addEventListener("install", (event) => {
  const cacheKey = "前端柒八九_v1";
  event.waitUntil(
    caches.open(cacheKey).then((cache) => {
      // 将数组中的所有资产添加到'前端柒八九_v1'的`Cache`实例中以供以后使用。
      return cache.addAll([
        "/css/global.bc7b80b7.css",
        "/css/home.fe5d0b23.css",
        "/js/home.d3cc4ba4.js",
        "/js/A.43ca4933.js",
      ]);
    })
  );
});

这会创建一个新的Cache实例并对资产进行预缓存。其中有一个event.waitUntilevent.waitUntil接受一个Promise,并等待该Promise被解决。

在这个示例中,这个Promise执行两个异步操作:

  • 创建一个名为前端柒八九_v1的新Cache实例
  • 在创建缓存之后,使用其异步的addAll方法预缓存一个资源URL数组

如果传递给event.waitUntilPromise拒绝,安装将失败。如果发生这种情况,service worker将被丢弃

如果Promise被解决,安装成功,service worker的状态将更改为installed,然后进入激活阶段。

相关文章
|
8月前
|
存储 Web App开发 Android开发
Service Worker 在 PWA 中的应用
Service Worker 在 PWA 中的应用
59 0
|
2月前
|
存储 缓存 算法
关于 Service Worker 和 Web 应用对应关系的讨论
关于 Service Worker 和 Web 应用对应关系的讨论
14 0
|
5月前
|
存储 Web App开发 缓存
WorkBox 之底层逻辑Service Worker(二)
WorkBox 之底层逻辑Service Worker(二)
|
7月前
|
存储 XML Java
Flowable:ProcessEngin(引擎)与Service(服务接口)讲解
Flowable:ProcessEngin(引擎)与Service(服务接口)讲解
118 0
|
8月前
|
缓存 JSON 自然语言处理
PWA 应用 Service Worker 缓存的一些可选策略和使用场景
PWA 应用 Service Worker 缓存的一些可选策略和使用场景
65 0
|
8月前
|
缓存 JavaScript 前端开发
在项目中使用Service Worker 与 PWA
在项目中使用Service Worker 与 PWA
46 1
|
12月前
|
存储 负载均衡 Kubernetes
简单说说K8S的Service底层,总感觉还是说不清楚。
简单说说K8S的Service底层,总感觉还是说不清楚。
148 0
|
域名解析 Kubernetes 负载均衡
k8s service 概念和原理
详细讲解k8s的概念和原理
707 0
k8s service 概念和原理
|
Kubernetes 网络协议 Docker
k8s service资源定义详解
详解Service类型的资源定义
185 0
|
编解码 缓存 Android开发
深入剖析Android四大组件(五)——并行执行的Service
深入剖析Android四大组件(五)——并行执行的Service
85 0