JavaScript工做原理(八):Service Workers,生命周期和应用案例

图片描述
您可能已经知道,渐进式Web应用(PWA)会愈来愈受欢迎,由于它们旨在使Web应用的用户体验更加流畅,建立Native应用程序般的体验,而不仅是运行在浏览器的应用。css

构建渐进式Web应用程序的主要缘由之一是使应用在网络和加载方面很是可靠 - 它应该可用于不肯定或不存在的网络条件。web

在这篇文章中,咱们将深刻探讨Service Worker:他们如何运做以及应该注意的地方。最后,咱们还列出了您应该利用的Service Workers的一些独特优点。promise

概述

若是你想了解关于Service Workers的一切,你应该首先阅读本系列第几篇文章。浏览器

基本上,Service Worker是一种网络工做者,更具体地说,它就像一个Shared Worker:缓存

  • Service Worker在它本身的全局脚本上下文中运行
  • 它没有绑定到特定的网页
  • 它没法访问DOM

Service Worker API使人兴奋的主要缘由之一是它可让你的网络应用程序支持离线体验,从而使开发人员可以彻底控制流程。安全

Service Worker 的生命周期

Service Worker生命周期与您的网页是彻底分开。它由如下几个阶段组成:网络

  • 下载
  • 安装
  • 激活

下载

这是浏览器下载包含Service Worker的.js文件的时候。并发

安装

您的Web应用程序想要安装Service Worker,您必须先注册它,您能够在JavaScript代码中进行注册。当Service Worker被注册时,它会提示浏览器在后台启动Service Worker安装步骤。app

经过注册Service Worker,你能够告诉浏览器你的Service Worker的JavaScript文件在哪里。咱们来看下面的代码:oop

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // 注册成功
      console.log('ServiceWorker registration successful');
    }, function(err) {
      // 注册失败
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}

该代码检查当前环境中是否支持Service Worker API。若是支持,则注册/sw.js Service Worker。

您能够在每次加载页面时调用register()方法而不用担忧 - 浏览器会判断Service Worker是否已经注册,而且会正确处理。

register()方法的一个重要细节是Service Worker文件的位置。在这种状况下,您能够看到服务工做者文件位于域的根目录。这意味着Service Worker的范围将是整个来源。换句话说,这个Service Worker将会收到这个域的全部东西的fetch事件(咱们将在后面讨论)。若是咱们在/example/sw.js注册Service Worker文件,那么服务工做者将只能看到URL以/example/(即/example/page1/,/example/page2/)开头的页面的fetch事件。

在安装阶段,最好加载和缓存一些静态资源。资源成功缓存后,Service Worker安装完成。若是没有成功(加载失败) - Service Worker将重试。一旦安装成功,您将知道静态资源位于缓存中。
在安装阶段,最好加载和缓存一些静态资源。资源成功缓存后,Service Worker安装完成。若是没有成功(加载失败) - Service将重试。一旦安装成功,您将知道静态资产位于缓存中。

若是注册须要在加载事件以后发生,这将回答您的问题。这不是必须的,但它是绝对推荐的。

为何这样?让咱们考虑用户第一次访问您的网络应用程序。目前尚未Service Worker,浏览器没法事先知道是否会有安装的Service Worker。若是安装了Service Worker,则浏览器须要为这个额外的线程花费额外的CPU和内存,不然浏览器将花费在渲染网页上。

最重要的是,若是你只是在你的页面上安装一个Service Worker,你可能会有延迟加载和渲染的风险 - 而不是尽快让你的用户可使用这个页面。

请注意,这仅在第一次访问页面时才显得重要。后续页面访问不受Service Worker安装的影响。一旦在第一次访问页面时激活Service Worker,它能够处理加载/缓存事件,以便随后访问您的Web应用程序。这一切都是有道理的,由于它须要准备好处理有限的网络链接。

激活

安装Service Worker以后,下一步是将其激活。这一步是管理之前缓存的好机会。

一旦激活,Service Worker将开始控制全部属于其范围的页面。一个有趣的事实是:首次注册Service Worker的页面将不会被控制,直到该页面再次被加载。一旦处于Service Worker控制之下,它将处于如下状态之一:

  • 它将处理从页面发出网络请求或消息时发生的fetch和message事件
  • 它将被终止以节省内存

如下是生命周期的外观:
图片描述

在ervice Worker内部处理安装

在页面加速注册过程以后,让咱们看看Service Worker脚本中发生了什么,它经过向Service Worker实例添加事件侦听器来处理install事件。

这些是install事件处理时须要采起的步骤:

  • 打开缓存
  • 缓存咱们的文件
  • 确认是否缓存了全部必需的资源

这是一个简单的install在Service Worker内:

var CACHE_NAME = 'my-web-app-cache';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/scripts/app.js',
  '/scripts/lib.js'
];

self.addEventListener('install', function(event) {
  // event.waitUntil takes a promise to know how
  // long the installation takes, and whether it 
  // succeeded or not.
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

若是全部文件成功被缓存,Service Worker会被安装。若是任何文件下载失败,install步骤会失败。因此主要注意你须要缓存的文件。

处理install事件是可选的,你能够避免处理它。在这种状况下,上面三个步骤你都无需处理。

在运行时缓存请求

这部分是关键所在。这里您将看到如何拦截请求并返回建立的缓存(并建立新缓存)。

安装Service Worker而且用户导航到另外一个页面或刷新他所在的页面后,Service Worker将收到fetch事件。下面是一个演示如何返回缓存资源或执行新请求而后缓存结果的示例:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    // This method looks at the request and
    // finds any cached results from any of the
    // caches that the Service Worker has created.
    caches.match(event.request)
      .then(function(response) {
        // If a cache is hit, we can return thre response.
        if (response) {
          return response;
        }

        // Clone the request. A request is a stream and
        // can only be consumed once. Since we are consuming this
        // once by cache and once by the browser for fetch, we need
        // to clone the request.
        var fetchRequest = event.request.clone();
        
        // A cache hasn't been hit so we need to perform a fetch,
        // which makes a network request and returns the data if
        // anything can be retrieved from the network.
        return fetch(fetchRequest).then(
          function(response) {
            // Check if we received a valid response
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            // Cloning the response since it's a stream as well.
            // Because we want the browser to consume the response
            // as well as the cache consuming the response, we need
            // to clone it so we have two streams.
            var responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                // Add the request to the cache for future queries.
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});

简而言之,这就是发生的过程:

  • event.respondWith()将决定咱们如何响应fetch事件。咱们传递了一个来自caches.match()的promise,它查看请求并发现是否有任何已建立的缓存的缓存结果。
  • 若是存在缓存,则返回结果。
  • 不然,将执行fetch事件。
  • 检查状态是否为200。咱们还检查响应类型是否basic,这代表它是来自咱们origin的请求。在这种状况下,不会缓存对第三方资源的请求。
  • 响应结果被添加到缓存中。

请求和响应必须被克隆,由于它们是流。流的主体只能被使用一次。并且因为咱们想要使用它们,浏览器也要使用它们,因此必需克隆它们。

更新服务工做者

当用户访问您的Web应用程序时,浏览器会尝试从新下载包含Service Worker代码的.js文件。这发生在后台。

若是与当前Service Worker的文件相比,如今下载的Service Worker文件中甚至存在单字节差别,则浏览器将假定有改变而且必须启动新的Service Worker。

新的Service Worker将启动而且install事件将被触发。然而,在这一点上,旧的Service Worker仍在控制你的网络应用程序的页面,这意味着新的Service Worker将进入waiting状态。

一旦您的Web应用程序当前打开的页面关闭,旧的Service Worker将被浏览器终止,新安装的Service Worker将彻底控制。这是当它的激活事件将被触发。

为何须要这些?为了不两个版本的Web应用程序同时运行在不一样的选项卡上 - 这在网络上实际上很是常见,而且可能会建立很是糟糕的错误(例如,在浏览器中存储数据时存在不一样模式的状况)。

从缓存中删除数据

activate回调中最多见的步骤是缓存管理。你如今要这样作,由于若是你在安装步骤中删除了全部旧的缓存,旧的Service Workers将忽然中止提供缓存中的文件。

下面是一个示例,您能够从缓存中删除某些未列入白名单的文件(在这种状况下,其名称中包含page-1或page-2):

self.addEventListener('activate', function(event) {

  var cacheWhitelist = ['page-1', 'page-2'];

  event.waitUntil(
    // Retrieving all the keys from the cache.
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        // Looping through all the cached files.
        cacheNames.map(function(cacheName) {
          // If the file in the cache is not in the whitelist
          // it should be deleted.
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

HTTPS要求

在构建Web应用程序时,您能够经过本地主机使用Service Worker,可是一旦将其部署到生产环境中,则须要准备好HTTPS(这是您拥有HTTPS的最后一个缘由)。

使用Service Worker,您能够劫持链接并制做响应。 经过不使用HTTPs,您的Web应用程序变得容易受到攻击。

为了让事情更安全,您须要在经过HTTPS提供的页面上注册Service Worker,以便您知道浏览器接收的Service Worker在经过网络中未被修改。

浏览器支持

服务人员的浏览器支持正在变得愈来愈好:
图片描述

Service Worker应用场景

Service Worker提供的一些独特功能是:

  • 推送通知 - 容许用户选择从网络应用程序及时更新。
  • 后台同步 - 容许您推迟操做,直到用户具备稳定的链接。这样,您能够确保不管用户想要发送什么,实际上都会发送。
  • 按期同步(未支持) - 提供管理按期后台同步功能的API。
  • Geofencing(将来支持) - 您能够定义参数,也称为围绕感兴趣区域的地理围栏。当设备跨越地理围栏时,Web应用程序会收到通知,这可让您根据用户的地理位置提供有用的体验。
相关文章
相关标签/搜索