原文请查阅 这里,略有删减,本文采用 知识共享署名 4.0 国际许可协议共享,BY Troland。
本系列持续更新中,Github 地址请查阅这里。javascript
这是 JavaScript 工做原理的第八章。css
或许你已经了解到渐进式网络应用将只会愈来愈流行,由于它旨在创造拥有更加流畅的用户体验的网络应用和建立类 App 的原生应用体验而非浏览器端那样的外观和体验。前端
构建渐进式网络应用的主要需求之一即在各类网络和数据加载的条件下仍然可用-它能够在网络不稳定或者没有网络的状况下使用。java
本文咱们将会深刻了解 Service Workers:它们是如何工做的以及你所应该关切的方面。最后,咱们将会列出一些Service Workers 可供利用的,独有的优点而且分享咱们在 SessionStack 中的团队实践经验。git
若想理解 Service Workers 相关的一切,你首先应该阅读一下以前发布的有关 Web Workers 的文章。github
大致上,Service Worker 是一种 Web Worker,更准确地说,它更像是一个 Shared Worker。web
Service Worker 接口之因此让人感到兴奋的缘由之一即它支持网络应用离线运行,这使得开发者可以彻底控制网络应用的行为。数据库
Service Worker 的生命周期和网页彻底不相关。它由如下几个步骤组成:promise
这发生于浏览器下载包含 Service Worker 相关代码的 .js
文件。浏览器
为了在网络应用中使用 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) { // 注册成功 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
事件。若是注册 service worker 的文件路径是 /example/sw.js
,那么 service worker 会接收到全部页面路径以 /example/
为开头的 URL 地址的 fetch
事件(好比 /example/page1/
/example/page2/
)。
在安装阶段,最好加载和缓存一些静态资源。一旦静态资源缓存成功,Service Worker 的安装也就完成了。假若加载失败-Service Worker 将会重试。一旦安装成功,静态资源也就缓存成功了。
这也就回答了为何要在 load 事件以后注册 Service Worker。这不是必须的,可是强烈推荐这么作。
为何要这样作呢?假设用户第一次访问网络应用。如今尚未注册 service worker,并且浏览器没法事先知晓是否会最终安装它。若是进行安装,则浏览器将会为增长的线程开辟额外的 CPU 和内存,而这些资源本来是用来渲染网页的。
参考下这里,load
事件会加载完全部的资源好比图片,样式以后触发。
最终的结果便是若是在页面中安装 Service Worker,将有可能致使页面延迟加载和渲染-不可以让用户尽快地访问网页。
须要注意的是这只会发生在第一次访问页面的时候。后续的页面访问不会被 Service Worker 的安装所影响。一旦在首次访问页面的时候激活了 Service Worker ,它就能够处理后续的页面访问所触发的页面加载/缓存事件。这是正确的,Service Worker 须要加载好以处理有限的网络带宽。
安装完以后下一步即激活。该步骤是操做以前缓存资源的绝佳时机。
一旦激活,Service Worker 就能够开始控制在其做用域内的全部页面。一个有趣的事实即:注册了 Service Worker 的页面直到再次加载的时候才会被 Service Worker 进行处理。当 Service Worker 开始进行控制,它有如下几种状态:
如下即其生命周期:
在页面运行注册 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 使用 promise 来得到安装时长及安装是否失败 event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); });
若是文件都成功缓存,则 service worker 安装成功。若是任意文件下载失败,那么 service worker 将会安装失败。所以,请注意须要缓存的文件。
处理 install
事件彻底是可选,当不进行处理的时候,跳过以上几个步骤便可。
该部分才是干货。在这里能够看到如何拦截请求而后返回已建立的缓存(以及建立新的缓存)。
当 Service Worker 安装完成以后,用户会导航到另外一个页面或者刷新当前页面,Service Worker 将会收到 fetch 事件。这里有一个演示了如何返回缓存的静态资源或执行一个新的请求并缓存返回结果的过程的示例:
self.addEventListener('fetch', function(event) { event.respondWith( // 该方法查询请求而后返回 Service Worker 建立的任何缓存数据。 caches.match(event.request) .then(function(response) { // 如有缓存,则返回 if (response) { return response; } // 复制请求。请求是一个流且只能被使用一次。由于以前已经经过缓存使用过一次了,因此为了在浏览器中使用 fetch,须要复制下该请求。 var fetchRequest = event.request.clone(); // 没有找到缓存。因此咱们须要执行 fetch 以发起请求并返回请求数据。 return fetch(fetchRequest).then( function(response) { // 检测返回数据是否有效 if(!response || response.status !== 200 || response.type !== 'basic') { return response; } // 复制返回数据,由于它也是流。由于咱们想要浏览器和缓存同样使用返回数据,因此必须复制它。这样就有两个流 var responseToCache = response.clone(); caches.open(CACHE_NAME) .then(function(cache) { // 把请求添加到缓存中以备以后的查询用 cache.put(event.request, responseToCache); }); return response; } ); }) ); });
大概的流程以下:
event.respondWith()
会决定如何响应 fetch
事件。 caches.match()
查询请求而后返回以前建立的缓存中的任意缓存数据并返回 promise。fetch
。200
。同时检查响应类型是否为 basic,即检查请求是否同域。当前场景不缓存第三方资源的请求。由于请求和响应都是流而流数据只能被使用一次,因此必须进行复制。并且因为缓存和浏览器都须要使用它们,因此必须进行复制。
当用户访问网络应用的时候,浏览器会在后台试图从新下载包含 Service Worker 代码的 .js
文件。
若是下载下来的文件和当前的 Service Worker 代码文件有一丁点儿不一样,浏览器会认为文件发生了改变而且会建立一个新的 Service Worker。
建立新的 Service Worker 的过程将会启动,而后触发 install
事件。然而,这时候,旧的 Service Worker 仍然控制着网络应用的页面意即新的 Service Worker 将会处于 waiting
状态。
一旦关闭网络应用当前打开的页面,旧的 Service Worker 将会被浏览器杀死而新的 Service Worker 就能够上位了。这时候将会触发 activate
事件。
为何全部这一切是必须的呢?这是为了不在不一样选项卡中同时运行不一样版本的的网络应用所形成的问题-一些在网页中实际存在的问题且有可能会产生新的 bug(好比当在浏览器中本地存储数据的时候却拥有不一样的数据库结构)。
activate
回调中最为常见的步骤即缓存管理。由于若想删除安装步骤中老旧的缓存,而这又会致使 Service Workers 没法获取该缓存中的文件数据,因此,这时候须要进行缓存管理。
这里有一个示例演示如何把未在白名单中的缓存删除(该状况下,以 page-1
或者 page-2
来进行命名):
self.addEventListener('activate', function(event) { var cacheWhitelist = ['page-1', 'page-2']; event.waitUntil( // 得到缓存中全部键 caches.keys().then(function(cacheNames) { return Promise.all( // 遍历全部的缓存文件 cacheNames.map(function(cacheName) { // 若缓存文件不在白名单中,删除之 if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) ); });
当处于开发阶段的时候,能够经过 localhost 来使用 Service Workers ,但当处于发布环境的时候,必须部署好 HTTPS(这也是使用 HTTPS 的最后一个缘由了)。
能够利用 Service Worker劫持网络链接和伪造响应数据。若是不使用 HTTPS,网络应用会容易遭受中间人 攻击。
为了保证安全,必须经过 HTTPS 在页面上注册 Service Workers,这样就能够保证浏览器接收到的 Service Worker 没有在传输过程当中被篡改。
Service Workers 拥有良好的浏览器兼容性。
你能够追踪全部浏览器的支持进程:
https://jakearchibald.github....
Service Worker 独有的功能:
这里提到的每一个功能将会该系列以后的文章中进行详细阐述。
咱们持续不断地工做以让 SessionStack 的交互体验尽量流畅,优化页面加载时间和响应时间。
当在 SessionStack 上重放用户会话或者实时流播放,SessionStack 界面会从服务器持续抓取数据从而为用户创造一个类缓冲的使用体验(相似视频缓冲那种)。再详细了解一些原理即一旦在网络应用中集成 SessionStack 库,它将会持续收集诸如 DOM 变化,用户交互,网络请求,未处理异常以及调试信息的数据。
当重放或者实时观看一个会话的时候,SessionStack 会返回全部数据以方便观察发生于用户浏览器的全部事件。(视觉上和技术上的)。全部的这一切都是即时发生的,由于咱们不想让用户等待。
因为数据是由前端抓取的,这个时候就可使用 Service Workers 来处理相似播放器重载和再次流传输全部数据的情形。处理缓慢的网络链接也是很是重要的。
参见维基百科关于流的定义,能够更好地理解这里的流的概念。
本系列持续更新中,Github 地址请查阅这里。