PWA实战(2)—Service Wroker

什么是Service Worker

一个Service Worker是一种类型的web worker。它本质上是一个JavaScript文件,它与主浏览器线程分开运行,拦截网络请求,缓存或从缓存中检索资源,以及传递推送消息。

由于工作者与主线程分开运行,因此Service Worker独立于与其关联的应用程序。这有几个后果:

  • 因为Service Worker没有阻塞(它被设计为完全异步)同步XHR并且localStorage不能在Service Worker中使用。

  • 当应用程序未处于活动状态时,Service Worker可以从服务器接收推送消息。这样,即使未在浏览器中打开,您的应用也会向用户显示推送通知。

  • Service Worker无法直接访问DOM。为了与页面通信,Service Worker使用postMessage()方法发送数据,并使用message事件监听器来接收数据。

关于Service Worker的注意事项:

  • Service Worker是一种可编程网络代理,可让您控制如何处理来自页面的网络请求。

  • Service Worker只能通过HTTPS运行。由于Service Worker可以拦截网络请求并修改响应,因此“中间人”攻击可能非常糟糕。

    注意:像Let's Encrypt这样的服务允许您免费获得SSL证书以便在服务器上安装。

  • Service Worker工作程序在不使用时变为空闲状态,并在下次需要时重新启动。您不能依赖事件之间持久存在的全局状态。如果存在需要在重新启动时保留和重用的信息,则可以使用IndexedDB数据库。

  • Service Worker广泛使用Promises

Service Worker能做什么

Service Worker使应用程序能够控制网络请求,缓存这些请求以提高性能,并提供对缓存内容的脱机访问。

Service Worker依赖于两个API来使应用程序脱机工作: Fetch(从网络检索内容的标准方法)和Cache(应用程序数据的持久内容存储)。这个Cache是持久的,独立于浏览器缓存或网络状态。

提高应用程序/站点的性能

缓存资源将使内容在大多数网络条件下加载更快。有关缓存策略的完整列表,请参阅 使用Caching files with the service workerThe Offline Cookbook

让您的应用“离线优先”

使用Service Worker内部的Fetch API,我们可以拦截网络请求,然后使用所请求资源以外的内容修改响应。当用户离线时,我们可以使用此技术从缓存中提供资源。请参阅 使用Service Worker缓存文件以获得此技术的实际操作经验。

充当高级功能的基础

Service Worker提供了使Web应用程序像本机应用程序一样工作的功能的起点。其中一些功能是:

  • Notifications API:使用操作系统的本机通知系统显示通知并与通信交互的方法。

  • Push API:一种API,可让您的应用订阅推送服务并接收推送消息。推送消息被传递给Service WorkerService Worker可以使用消息中的信息来更新本地状态或向用户显示通知。由于Service Worker独立于主应用程序运行,因此即使浏览器未运行,他们也可以接收和显示通知。

  • Background Sync API:允许您将操作推迟到用户具有稳定连接。这对于确保实际发送用户想要发送的内容非常有用。此API还允许服务器定期更新应用程序,以便应用程序可以在下次联机时进行更新

  • Channel Messaging API:允许Web工作者和Service Worker相互通信并与主机应用程序通信。此API的示例包括新内容通知和需要用户交互的更新。

Service Worker生命周期

Service Worker在其生命周期中经历三个步骤:

  • 注册
  • 安装
  • 激活

注册和作用域

要安装Service Worker,您需要在主JavaScript代码中注册它。注册会告诉浏览器您的Service Worker所在的位置,并开始在后台安装它。我们来看一个例子:

1
2
3
4
5
6
7
8
9
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Registration successful, scope is:', registration.scope);
})
.catch(function(error) {
console.log('Service worker registration failed, error:', error);
});
}

此代码首先检查浏览器支持navigator.serviceWorker。然后向Service Worker注册,navigator.serviceWorker.register返回在Service Worker成功注册时解析的promise,然后用记录registration.scope

scope是确定哪些Service Worker控制的,换句话说,从该路径的Service Worker将拦截请求。默认范围是Service Worker文件的位置,并扩展到下面的所有目录。因此,如果service-worker.js位于根目录中,则Service Worker将控制来自此域的所有文件的请求。

您还可以通过在注册时传入其他参数来设置任意范围。例如:

1
2
3
navigator.serviceWorker.register('/service-worker.js', {
scope: '/app/'
});

在这种情况下,我们设置Service Worker的作用域到/app/,这意味着Service Worker作用域包括/app//app/lower/以及/app/lower/lower等,但不包括/app或者/,以及其他上一级的路径。

如果已安装Service Worker,则navigator.serviceWorker.register返回当前Service Worker的注册对象。

安装

一旦浏览器注册了Service Worker,就可以尝试安装。如果浏览器认为Service Worker是新用户,则会安装Service Worker,原因是该站点当前没有已注册的Service Worker,或者因为新Service Worker与之前安装的Service Worker之间存在字节差异。

Service Worker安装会在安装过程中触发事件。我们可以在Service Worker安装过程中包含一个事件监听器,以便在安装Service Worker时执行某些任务。例如,在安装过程中,Service Worker可以预先缓存Web应用程序的某些部分,以便在用户下次打开它时立即加载。因此,在第一次加载之后,您将从即时重复加载中受益,并且在这些情况下,您的交互时间将变得更好。安装事件侦听器的示例如下所示:

1
2
3
4
// Listen for install event, set callback
self.addEventListener('install', function(event) {
// Perform some task
});

激活

一旦Service Worker成功安装,它就会转换到激活阶段。如果存在由前一个Service Worker控制的任何打开页面,则新Service Worker进入waiting状态。新Service Worker仅在不再加载仍在使用旧Service Worker的任何页面时激活。这可确保在任何给定时间只运行一个版本的Service Worker

注意:仅刷新页面不足以将控制权转移给新的Service Worker,因为在卸载当前页面之前将请求新页面,并且不会有旧Service Worker未使用的时间。
当新Service Worker激活时,将激活的Service Worker中触发事件activate。此事件侦听器是清理过时缓存的好地方

1
2
3
self.addEventListener('activate', function(event) {
// Perform some task
});

激活后,Service Worker将控制在其作用域内加载的所有页面,并开始侦听来自这些页面的事件。但是,在Service Worker激活之前加载的页面不在Service Worker控制之下。新Service Worker只会在您关闭并重新打开应用程序或执行 clients.claim()时接管。在此之前,新Service Worker不会截获此页面的请求。这是有意识的,以确保您的网站的一致性。