[译] JavaScript 是如何工做的:Service Worker 的生命周期与使用场景

这是专门探索 JavaScript 及其构建组件的系列的第八个。在识别和描述核心元素的过程当中,咱们也分享了一些咱们在构建 SessionStack 时的最佳实践。SessionStack 是一个强大且性能卓越的 JavaScript 应用程序,能够向你实时显示用户在 Web 应用程序中遇到技术问题或用户体验问题时的具体状况。javascript

若是你没看过以前的章节,你能够在这里看到:css

  1. [译] JavaScript 是如何工做的:对引擎、运行时、调用堆栈的概述
  2. [译] JavaScript 是如何工做的:在 V8 引擎里 5 个优化代码的技巧
  3. [译] JavaScript 是如何工做的:内存管理 + 处理常见的4种内存泄漏
  4. [译] JavaScript 是如何工做的: 事件循环和异步编程的崛起 + 5个如何更好的使用 async/await 编码的技巧
  5. [译] JavaScript 是如何工做的:深刻剖析 WebSockets 和拥有 SSE 技术 的 HTTP/2,以及如何在两者中作出正确的选择
  6. [译] JavaScript 是如何工做的:与 WebAssembly 一较高下 + 为什么 WebAssembly 在某些状况下比 JavaScript 更为适用
  7. [译] JavaScript 是如何工做的:Web Worker 的内部构造以及 5 种你应当使用它的场景

你可能已经知道了渐进式 Web 应用只会愈来愈受欢迎,由于它们旨在使 Web 应用的用户体验更加流畅,提供原生应用体验而不是浏览器的外观和感受。前端

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

在这篇文章中,咱们将深刻探讨 Service Worker:它们如何运做以及开发者应该关心什么。最后,咱们还列出了开发者应该利用的 Service Worker 的一些独特优点,并在 SessionStack 中分享咱们本身团队的经验。android

概览

若是你想了解 Service Worker 的一切内容,你应该从阅读咱们博客上,关于 Web Workers 的文章开始。ios

基本上,Service Worker 是 Web Worker 的一个类型,更具体地说,它像 Shared Workergit

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

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

Service Worker 的生命周期

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

  • 下载
  • 安装
  • 激活

下载

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

安装

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

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

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // Registration was successful
      console.log('ServiceWorker registration successful');
    }, function(err) {
      // Registration failed
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}
复制代码

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

每次页面加载时均可以调用 register() 方法,浏览器会判断 Service Worker 是否已经注册,而且会正确处理。

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

在安装阶段,最好加载和缓存一些静态资源。资源成功缓存后,Service Worker 安装完成。若是没有成功(加载失败)—— Service Worker 将重试。一旦安装成功,静态资源就已经在缓存中了。

若是注册须要在加载事件以后发生,这就解答了你“注册是否须要在加载事件以后发生”的疑惑。这不是必要的,但绝对是推荐的。

为何这样呢?让咱们考虑用户第一次访问网络应用程序的状况。当前尚未 Service Worker,浏览器没法事先知道最终是否会安装 Service Worker。若是安装了 Service Worker,则浏览器须要为这个额外的线程承担额外的 CPU 和内存开销,不然浏览器会将计算资源用于渲染网页上。

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

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

激活

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

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

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

如下是生命周期的示意图:

处理 Service Worker 内部的装置

在页面处理注册过程以后,让咱们看看 Service Worker 脚本中发生了什么,它经过向 Service Worker 实例添加事件监听来处理 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 事件彻底是可选的,你能够避免它,在这种状况下,你不须要执行这里的任何步骤。

运行时缓存请求

这部分是货真价实的内容。你将看到如何拦截请求并返回建立的缓存(以及建立新缓存)的位置。

在安装 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,代表响应来自咱们最初的请求。在这种状况下,不会缓存对第三方资源的请求。
  • 响应被缓存下来

请求和响应必须被复制,由于它们是。流的 body 只能被使用一次。而且因为咱们想消费它们,而浏览器也必须消费它们,所以咱们便须要克隆它们。

上传 Service Worker

当有一个用户访问你的 web 应用,浏览器将尝试从新下载包含了 Service Worker 的 .js 文件。这将在后台执行。

若是与当前 Service Worker 的文件相比,新下载的 Service Worker 文件中存在哪怕一个字节的差别,则浏览器将会认为有变动,且必须启动新的 Service Worker。

新的 Service Worker 将启动而且安装事件将被移除。然而,在这一点上,旧的 Service Worker 仍在控制你的 web 应用的页面,这意味着新的 Service Worker 将进入 waiting 状态。

一旦你的 web 应用程序当前打开的页面都被关掉,旧的 Service Worker 就会被浏览器干掉,西南装的 Service Worker 将彻底掌控应用。这就是它激活的事件将被干掉的时候。

为何须要这些?为了不两个版本的 Web 应用程序同时运行在不一样的 tab 上 —— 这在网络上实际上很是常见,而且可能会产生很是糟糕的错误(例如,在浏览器中本地存储数据时,会有不一样的 schema)。

从缓存中删除数据

activate 回调中最多见的步骤是缓存管理。咱们应该如今作这件事,由于若是你在安装步骤中清除了全部旧缓存,旧的 Service Worker 将忽然中止提供缓存中的文件。

这里提供了一个如何从缓存中删除一些不在白名单中的文件的例子(在本例中,有 page-1page-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 的支持正在变得愈来愈好:

你能够在这个网站上追踪全部浏览器的适配进程 —— jakearchibald.github.io/isservicewo…

Service Workers 正在打开美好特性的大门

Service Worker 提供的一些独一无二的特性:

  • 推送通知 —— 容许用户选择从 web 应用程序及时获取通知。
  • 后台同步 —— 在用户网络不稳定时,容许开发者推迟操做,直到用户具备稳定的链接。这样,就能够确保不管用户想要发送什么数据,均可以发出去。
  • 定时同步(将来支持)—— 提供管理按期后台同步功能的 API。
  • 地理围栏(将来支持)—— 开发者能够自定义参数,建立感兴趣区域的地理围栏。当设备跨越地理围栏时,Web 应用程序会收到通知,这可让开发者根据用户的地理位置提供有效服务。

这些将在本系列将来的博客文章中详细讨论。

咱们一直致力于使 SessionStack 的用户体验尽量流畅,优化页面加载和响应时间。

当你在 SessionStack(或实时观看)中重播用户会话时,SessionStack 前端将不断从咱们的服务器提取数据,以便无缝地建立缓冲区,像你刚才,同本文中同样的经历。为了提供一些上下文 —— 一旦你将 SessionStack 的库集成到 Web 应用程序中,它将不断收集诸如 DOM 更改,用户交互,网络请求,未处理的异常和调试消息等数据。

当会话正在重播或实时流式传输时,SessionStack 会提供全部数据,让开发者能够在视觉和技术上查看用户在本身的浏览器中体验到的全部内容。这一切都须要快速实现,由于咱们不想让用户等待。

因为数据是由咱们的前端提取的,所以这是一个很好的地方,能够利用 Service Worker 来从新加载咱们的播放器,以及从新传输数据流等状况。处理较慢的网络链接也很是重要。

若是你想尝试 SessionStack,这有个免费的计划

参考资料


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索