service work是什么
service work其实就是一段脚本,运行在一个独立的线程中,和负责解析当前网页工作的主线程都属于当前页面进程 。更加具体形象一点的理解可以认为它是一个运行在应用程序与浏览器之间的一个代理服务器。在Chrome浏览器地址栏输入chrome://inspect/#service-workers,可以看到你目前打开的网页中支持service work的网站 Service Worker 有以下特性:
- 独立的工作线程,不会阻塞js主线程。
- 不能直接进行操作 DOM,BOM等操作。
- 它是一个可编程的网络代理,让你可以控制页面请求的处理方式
- 一旦被 install,就永远存在,除非被手动 unregister。
- 必须在 HTTPS 环境下才能工作(localhost除外)
可以在浏览器的任务管理器中查看当前页面的servicework线程
service work存在的意义
servicework可以实现很多功能:
- 离线缓存静态资源
- 网络拦截、代理,转发请求,伪造响应
- 消息推送、传递
- 执行耗时任务,如大量的数据运算。
servicework在实际中的用处就是用来做离线访问,实现在断网的情况下也能正常的访问webapp。现在听得比较多的PWD就是依赖servicework实现的,它是一种Web App新模型,它可以实现在没有网络的环境中也能够提供基本的页面访问。
servicework 工作流程
- Service Worker 文件只在首次注册的时候执行了一次。
- 安装、激活流程也只是在首次执行 Service Worker 文件的时候进行了一次。
- 首次注册成功的 Service Worker 没能拦截当前页面的请求。
- 非首次注册的 Service Worker 可以控制当前的页面并能拦截请求
- Service Worker 首次注册或者有新版本触发更新的时候,才会重新创建一个 worker 工作线程并解析执行 Service Worker 文件,在这之后并进入 Service Worker 的安装和激活生命周期。
service work的生命周期
当一个servicework被注册成功后,它将开始它的生命周期,我们对servicework的操作一般都是在其生命周期里面进行的。下面是一个servicework初始化时候的生命周期
可以看到servicework的生命周期分为这么几个状态 安装中, 安装后, 激活中, 激活后, 废弃
-
安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,这个状态会触发 install 事件,一般会在install事件的回调里面进行静态资源的离线缓存, 如果这些静态资源缓存失败了,那 Service Worker 安装就会失败,生命周期终止。
-
安装后( installed ):当成功捕获缓存到的资源时,servicework会变为这个状态,当此时没有其他的servicework线程在工作时,会立即进入激活状态,如果此时有正在工作的servicework工作线程,则会等待其他的 Service Worker 线程被关闭后才会被激活。可以使用 self.skipWaiting() 方法强制正在等待的servicework工作线程进入激活状态。
-
激活( activating ):在这个状态下会触发activate事件,在activate 事件的回调中去清理旧版缓存。
-
激活后( activated ):在这个状态下,servicework会取得对整个页面的控制
-
废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。新版本的 Service Worker 替换了旧版本的 Service Worker会出现这个状态
更新 service worker
更新一个servicework,最直接的办法就是修改servicework.js这个文件,当刷新浏览器时,浏览器尝试重新下载servicework.js脚本文件,然后会与之前的版本比对,一旦发现文件内容不一致,就会进入更新流程,更新一个 service work 的流程大致如下:
- 新的 servicework 被启动安装并触发 install事件。
- 安装成功后,新版 servicework 进入等待状态,此时页面的控制权还在老版 servicework手中。
- 当servicework控制的所有终端都关闭之后,或者手动self.skipWaiting(),旧版 servicework 才能被终止,此时新版servicework被激活,触发activate 。
- 用户再次访问页面,或刷新页面,新版 service work 启动控制页面。
简单使用servicework
-
注册servicework,serviceWorker对象存在于navigator对象下,可以再主线程中调用navigator.serviceWorker.register()方法来注册servicework,第一个参数表示servicework.js相对于origin的路径, scope参数是选填的,用来指定你想让 service worker 控制的内容的目录。 默认值为servicework.js所在的目录。 成功注册或返回一个promise。
if (navigator.serviceWorker) { //为了减少性能损耗,一般直接在onload事件里面注册 window.addEventListener('load', () => { navigator.serviceWorker.register('./servicework.js',{ scope: './' }) .then((reg) => { //注册成功 console.log('ServiceWorker registration success: ', reg); }) .catch((err) => { //注册失败 console.log('ServiceWorker registration failed: ', err); }); }); } -
安装servicework
self.addEventListener('install',(event) => { self.skipWaiting(); //用来强制更新的servicework跳过等待时间 event.waitUntil( caches.open('cache_v1') .then((cache) => { return cache.addAll([ './app.js', './1.png', './index.html', '/getdata', './style.css', ]); }) ); });上面代码表示,首先self.skipWaiting()执行,告知浏览器直接跳过等待阶段,淘汰过期的Service Worker脚本,直接开始尝试激活新的Service Worker。然后使用caches.open打开一个Cache,打开后,通过cache.addAll尝试缓存我们预先声明的文件。
event.waitUntil() 方法用来延长事件的作用时间,该方法接收一个 Promise 参数,通常会将安装的回调执行逻辑封装在一个 Promise 里,如果操作报错应该通过 Promise 来 reject 错误,这样 Service Worker 就知道了安装失败,然后 Service Worker 就能中断生命周期。这样做的原因是由于 Service Worker 生命周期异步触发的特性,并不是像同步执行模式,如果报错就会中断执行 , 所以说并不是 intall 回调中出错了就会导致生命周期中断。
在 install 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 installing 状态,直到传递的 Promise 被成功地 resolve。这主要用于确保:Service Worker 工作线程在所有依赖的核心 cache 被缓存之前都不会被激活。
在 activate 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 activating 状态,直到传递的 Promise 被成功地 resolve。这主要用于确保:任何功能事件不会被分派到 ServiceWorkerGlobalScope 对象,直到它删除过期的缓存条目。
当 waitUntil()运行时,如果 Promise 是 rejected,installing 或者 activating 的状态会被设置为 redundant。
-
更新servicework
self.addEventListener('activate', (event) => { var cacheWhitelist = [CACHE_NAME]; self.clients.claim(); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) =>{ if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) ); });在更新servicework时需要删除之前的缓存,可将需要的缓存放在有个白名单中,然后通过caches.keys()拿到所有缓存,将不再白名单中的缓存删掉。
self.clients.claim可以保证 Service Worker 激活之后能够马上作用于所有的终端
-
监听拦截网络请求
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((response) => { if (response) return response; } return fetch(event.request); }) ); });可以通过监听servicework的fetch事件来拦截网络请求,调用 event 上的 respondWith() 方法来劫持当前servicework控制域下的 HTTP 请求,该方法会直接返回一个Promise 结果 ,这个接货就会是http请求的响应。上面代码中就一个简单的逻辑,先劫持http请求,然后看看缓存中是否有这个请求的资源,如果有则直接返回,如果没有就去请求服务器上的资源。
serrvicework和http缓存(就静态资源)
这里的http缓存方式默认为除所有html文件以外的静态资源都强制缓存,html文件协商缓存。****
在请求方面:
- 每次刷新,一般http缓存会有一次html文件的请求,而sw则会有一次sw.js文件的请求。(都是304)
- 断网情况下: sw仍然可以获取缓存,httpcache则会因无法获取html而导致所有资源无法获取。
- sw缓存类似 cache-control 的值,缓存也没有过期时间这个说法。也就是说,除非重新发包,否则会一直从缓存拿。
请求速度方面:(以下拿vue官网index.html做的数据)
在缓存文件更新方面:
-
如果是http缓存,一刷新页面就可以拿到最新的资源
-
如果是sw缓存, 一刷新页面,会返回当前缓存中的资源(不是最新),然后请求sw.js文件发现更新后重新进入sw生命周期,重新去更新缓存,当你再次刷新时才能拿到最新资源。
总结: 在缓存资源更新时,sw会延迟一次刷新才能获取最新资源
在控制缓存:
http一般是由服务器端控制的,而sw则是可以前端自己控制,可以更好地控制缓存。