-
ServiceWorker是什么
Service worker是一个注册在指定源和路径下的事件驱动worker,它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。
-
worker是什么?
使用构造函数(例如,Worker())创建一个 worker 对象, 构造函数接受一个 JavaScript文件URL — 这个文件包含了将在 worker 线程中运行的代码
通过使用Web Workers,Web应用程序可以在独立于主线程的后台线程中,运行一个脚本操作。 这样做的好处是可以在独立线程中执行费时的处理任务,从而允许主线程(通常是UI线程)不会因此被阻塞/放慢。
特点:
(1)同源限制 分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。(同一协议和同一域名下面) (2)DOM 限制 Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象, 也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。 (3)通信联系 Worker 线程和主线程不在同一个上下文环境(这个环境对应的对象为DedicatedWorkerGlobalScope,Dedicated workers 由单个脚本使用; Shared workers使用SharedWorkerGlobalScope。),它们不能直接通信,必须通过消息完成。(举个例子) worker 上下文中大部分 window 对象的方法和属性是可以使用的,包括 WebSockets, 以及诸如 IndexedDB 和 FireFox OS 中独有的 Data Store API 这一类数据存储机制。 (4)脚本限制 Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。 (5)文件限制 Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。 注意:改网络请求的能力暴露给中间人攻击会非常危险。所以出于安全考量,Service workers只支持HTTPS
tip: WorkerGlobalScope更多信息请参见: Functions and classes available to workers 。
-
Worker与shareWorker
WebWork分有俩个类型, Worker与shareWorker
专有类型:Dedicated Worker 共享类型:Shared Worker 他们之间有什么区别?
- 最明显的区别,shared Worker 可以共享,worker 不能.体现在如下方便
- 来自同源的脚本可以都用访问同一个 shareWorker 对象用作通讯,而 worker 对象只能被他创建的脚本访问,通讯
- 作用域不同:
- 所有shareWorker对象共享同一个作用域
- worker 对象的作用域与创建他的主进程相关联.
- 共享类型必须通过打开的活动端口实现通讯.(在专用worker中这一部分是隐式进行的)
//显示打开 myWorker.port.addEventListener("message", function(event) { alert(event.data); }, false ); myWorker.port.start(); //隐式打开:直接使用 onmessage myWorker.port.onmessage=function(event){ alert(event.data); }
- 最明显的区别,shared Worker 可以共享,worker 不能.体现在如下方便
-
ShareWorker与ServiceWorker
与开头所述,serviceWorker也是一种 worker.所以他继承了 Worker 的所有属性和特点
- serviceWorker与shareWorker一样
- 运行在全局workers上下文里(拥有自己的线程)
- 没有绑定在特有的页面上
- 与 shareWorker 不同的是:
- seriveWorker不依赖任何页面(当 shareWorker 被引用的页面都关闭之后,shareWorker 也就关闭了)
- seriveWorker 有更长的生命周期,
- seriveWorker 有自己的模块更新机制
具体不同见:www.w3.org/TR/service-…
- serviceWorker与shareWorker一样
-
关于如何杀死 worker 进程,错误监听等具体可以参考 developer.mozilla.org/zh-CN/docs/…
-
ServiceWorker
ServiceWorker可以不依赖任何页面,一旦注册可以一直存在(除非手动销毁)并作用于所在域内用户可访问的URL.
sw 可以使你的应用先访问本地缓存资源,所以在离线状态时,在没有通过网络接收到更多的数据前,仍可以提供基本的功能(一般称之为 Offline First)。
特点:
- ServiceWorker也是 worker,故会有 Dom 限制,无法直接操作 Dom;但可以通过 postMessage发送消息控制相关页面操作Dom
- ServiceWorker缓存机制依赖Cache API实现
- ServiceWorker 都是基于 promise 接口编程
- ServiceWorker 依赖 html5 的fetch API做网络编程
- ServiceWorker必须运行在 https下(保证安全性)
生命周期
事件
如何实现离线?
ServiceWorker 是浏览器和网络之间的虚拟代理,在其worker上下文中,可以通过 fetch 事件监听所有sw作用域下的资源请求动作,通过 event.respondWith() 方法来劫持HTTP响应,自定义返回.
具体步骤如下:
注册
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js', { scope: './' }).then(function(reg) { // registration worked console.log('Registration succeeded. Scope is ' + reg.scope); }).catch(function(error) { // registration failed console.log('Registration failed with ' + error); }); }
安装和激活:填充缓存
这里需要注意的是 cache.addAll 一个路径不对就会全部缓存失败.
//在 sw.js 中 this.addEventListener('install', function(event) { event.waitUntil( caches.open('v1').then(function(cache) { return cache.addAll([ '/', '/index.html', '/style.css', '/app.js', '/image-list.js', '/star-wars-logo.jpg', '/gallery/', '/gallery/bountyHunters.jpg', '/gallery/myLittleVader.jpg', '/gallery/snowTroopers.jpg' ]); }) ); });
自定义请求响应
对于请求我们我们首先会在缓存中查找资源是否被缓存,如果有,将会返回缓存的资源,如果不存在,会转而从网络中请求数据,然后将它缓存起来,这样下次有相同的请求发生时,我们就可以直接使用缓存。这种策略是缓存优先
self.addEventListener('fetch', function(event) { // caches.match(event.request):请求通过 url匹配 cache中的资源 event.respondWith(caches.match(event.request).then(function(response) { //这里response已经是访问缓存后的返回了. // caches.match() always resolves // but in case of success response will have value if (response !== undefined) { return response; } else { return fetch(event.request).then(function (response) { // response may be used only once // we need to save clone to put one copy in cache // and serve second one let responseClone = response.clone(); caches.open('v1').then(function (cache) { cache.put(event.request, responseClone); }); return response; }).catch(function () { return caches.match('/gallery/myLittleVader.jpg'); }); } })); });
这样我们就实现了离线缓存了.
更新,清理
-
更新缓存 版本总会有更新资源的时候 我们应该如何去更新它的Service Worker?我们存放在缓存名称中的版本号是这个问题的关键:
let cacheName='v1'
在把版本号更新为 v2 的时候,线程中会有一个新的Service Worker安装,并将最新资源缓存在 v2 的 cache 中.而旧的service worker仍然会正确的运行,直到没有任何页面使用到它为止,这时候新的service worker将会被激活.触发activate事件:
-
清理缓存
self.addEventListener('activate', function(event) { var cacheWhitelist = [cacheName]; event.waitUntil( caches.keys().then(function(keyList) { return Promise.all(keyList.map(function(key) { //删除白名单之外的缓存 if (cacheWhitelist.indexOf(key) === -1) { return caches.delete(key); } })); }) ); });
卸载
当我们网站应用不再需要 serviceWorker 的时候,如何卸载?在下个版本不注册serviceWorker? 正确的做法是:我们可以在新版本里使用unregister卸载.
if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { registration.unregister(); }); }
这样就好了吗? 注意:registration.unregister()会卸载掉 sw,但是并不会删除我们之前的缓存文件,所以在卸载sw之前,我们必须干掉在之前serviceWorker中用到的 IndexedDB,Storage,Caches
缓存策略
以下最为保守的缓存策略。
- HTM:如果你想让页面离线可以访问,使用 NetworkFirst,如果不需要离线访问,使用 NetworkOnly,其他策略均不建议对 HTML 使用。
- CSS 和 JS:情况比较复杂,因为一般站点的 CSS,JS 都在 CDN 上,SW 并没有办法判断从 CDN 上请求下来的资源是否正确(HTTP 200),如果缓存了失败的结果,问题就大了。这种我建议使用 Stale-While-Revalidate 策略,既保证了页面速度,即便失败,用户刷新一下就更新了。 如果你的 CSS,JS 与站点在同一个域下,并且文件名中带了 Hash 版本号,那可以直接使用 Cache First 策略。
- 图片: 建议使用 Cache First,并设置一定的失效事件,请求一次就不会再变动了。
注意:对于不在同一域下的任何资源,绝对不能使用 Cache only 和 Cache first。
更多缓存策略请见离线指南
调试
chrome访问chrome://inspect/#service-workers或chrome://serviceworker-internals查看service-workers firefox通过about:debugging#workers查看service-workers
兼容性
Desktop
Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari (WebKit) Basic support 40.0 33.0 (33.0)[1] 未实现 24 未实现 Mobile
Feature Android Chrome for Android Firefox Mobile (Gecko) Firefox OS IE Phone Opera Mobile Safari Mobile Basic support 未实现 40.0 (Yes) (Yes) 未实现 (Yes) 未实现 兼容方案:
- 关于 service workers 一个很棒的事情就是,如果你用像上面一样的浏览器特性检测方式发现浏览器并不支持。与此同时,如果你在一个页面上同时使用 AppCache 和 SW , 不支持 SW 但是支持 AppCache 的浏览器,可以使用 AppCache,如果都支持的话,则会采用 SW
- 使用ServiceWorker cache polyfill让旧版本浏览器支持 ServiceWorker cache API,
关于ServiceWorker的一些其他应用
- 1 后台数据同步(sync 事件)
- 2 响应推送(push 事件)
- 3 性能增强(比如预取用户可能需要的资源,比如相册中的后面数张图片)
实际应用
从开头对Service Worker的介绍就知道
ServiceWorker可以不依赖任何页面,一旦注册可以一直存在(除非手动销毁)并作用于所在域内用户可访问的URL
因此,我们在网络编程中的缓存策略尤为重要,而所有站点 Service Worker 的 install 和 active 都差不多,无非是做预缓存资源列表,更新后缓存清理的工作,逻辑不太复杂.
针对这种情况,我们更多的使用是Google 官方的 PWA 框架:Workbox3代替,它正是解决Service Worker Api( install、active、 fetch 事件做相应逻辑处理等)过于复杂的问题.
谁在使用
淘宝: PC 首页的 Service Worker 上线已经有一段时间了,经过不断地对缓存策略的调整,收益还是比较明显的,页面总下载时间从平均 1.7s,下降到了平均 1.4s,缩短了近 18% 的下载时间。(2018-08-09)
蓝狐:蓝狐是我们经常用的编辑查看原型文档应用,里面有很多图片等静态资源,非常适合使用 SW缓存
CSDN:在使用CSDN的时候经常有消息推送就是使用 SW 实现的
写到最后
这次分享主要是给大家介绍Service Worker的基础知识,实现离线应用的原理.希望大家可以从中有所收获.
注:PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。这些应用无处不在、功能丰富,使其具有与原生应用相同的用户体验优势。
参考文献