Service Worker简介

2022-04-24  本文已影响0人  涅槃快乐是金

Service Worker的由来

W3C 组织早在 2014 年 5 月就提出过 Service Worker 这样的一个 HTML5 API ,主要用来做持久的离线缓存。service worker是浏览器的一个高级特性,本质是一个web worker,是独立于网页运行的脚本。 web worker这个api被造出来时,就是为了解放主线程。因为,浏览器中的JavaScript都是运行在单一个线程上,随着web业务变得越来越复杂,js中耗时间、耗资源的运算过程则会导致各种程度的性能问题。 而web worker由于独立于主线程,则可以将一些复杂的逻辑交由它来去做,完成后再通过postMessage的方法告诉主线程。 service worker则是web worker的升级版本,相较于后者,前者拥有了持久离线缓存的能力。

Service Worker的特点

Service Worker的兼容性

下图是Service worker现有的浏览器支持版本, 从图上可以看出火狐和谷歌的支持是比较良好的,IE和safari需要相对比较高的版本才能够支持。移动端的话ios也需要从ios13才开始支持在安卓上的支持会相对广泛一点。


Service Worker的工作流程

Service Worker的生命周期

当一个servicework被注册成功后,它将开始它的生命周期,我们对servicework的操作一般都是在其生命周期里面进行的。servicework的生命周期分为这么几个状态 安装中, 安装后, 激活中, 激活后, 废弃。

更新Service Worker

更新一个servicework,最直接的办法就是修改servicework.js这个文件,当刷新浏览器时,浏览器尝试重新下载servicework.js脚本文件,然后会与之前的版本比对,一旦发现文件内容不一致,就会进入更新流程。

Service Worker 的简单实践

  1. 注册。 serviceWorker对象存在于navigator对象下,可以再主线程中调用navigator.serviceWorker.register()方法来注册servicework,register 方法接受两个参数,第一个参数表示servicework.js相对于origin的路径,第二个参数是 Serivce Worker 的配置项,可选填,其中比较重要的是 scope 属性,用来指定你想让 service worker 控制的内容的目录。 默认值为servicework.js所在的目录。这个属性所表示的路径不能在 service worker 文件的路径之上,默认是 Serivce Worker 文件所在的目录。 成功注册或返回一个promise。
// 页面的入口文件

if (navigator.serviceWorker) {
  window.addEventListener('load', () => {
    console.log('开始注册ServiceWorker')
    navigator.serviceWorker
      .register('./serviceworker.js')
      .then((reg) => {
        console.log('ServiceWorker register success: ', reg)
      })
      .catch((err) => {
        console.log('ServiceWorker register failed: ', err)
      })
  })
}

  1. 安装
self.addEventListener('install', (event) => {
  console.log('install事件')
  self.skipWaiting() //用来强制更新的servicework跳过等待时间
  event.waitUntil(
    caches.open(CACHE_NAME).then(function (cache) {
      return cache.addAll(urlsToCache)
    })
  )
})

首先 self.skipWaiting() 执行,告知浏览器直接跳过等待阶段,淘汰过期的Service Worker脚本,直接开始尝试激活新的Service Worker。然后使用 caches.open 打开一个Cache,打开后,通过cache.addAll尝试缓存我们预先声明的文件。 CacheStorage 全局的cache Api 并非只有在sw中才能用 浏览器控制台直接用也是可以的,所以是挂在window下的

event.waitUntil() 只能在 Service Worker 的 install 或者 activate 事件中使用;看起来像是一个 callback,用来延长事件的作用时间,但是,即便你不使用它,程序也可能正常运行。如果你传递了一个 Promise 给它,那么只有当该 Promise resolved 时,Service Worker 才会完成 install;如果 Promise rejected 掉,那么整个 Service Worker 便会被废弃掉。因此,cache.addAll 里面,只要有一个资源获取失败,整个 Service Worker 便会失效。

在 install 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 installing 状态,直到传递的 Promise 被成功地 resolve。这主要用于确保:Service Worker 工作线程在所有依赖的核心 cache 被缓存之前都不会被激活。

在 activate 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 activating 状态,直到传递的 Promise 被成功地 resolve。这主要用于确保:任何功能事件不会被分派到 ServiceWorkerGlobalScope 对象,直到它删除过期的缓存条目。

当 waitUntil()运行时,如果 Promise 是 rejected 那么installing 或者 activating 的状态会被设置为 redundant。

  1. 激活
self.addEventListener('activate', (event) => {
  console.log('activate事件')
  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()拿到所有缓存,将不再白名单中的缓存删掉。

  1. 拦截网络请求
  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请求,然后看看缓存中是否有这个请求的资源,如果有则直接返回,如果没有就去请求服务器上的资源。 event.respondWith 方法只能在 Service Worker 的 fetch 事件中使用。

Cache Stroage 只能缓存静态资源,所以它只能缓存用户的 GET 请求;Cache Stroage 中的缓存不会过期,但是浏览器对它的大小是有限制的,所以需要我们定期进行清理。
对应post 请求我们也可以通过 fetch方法拦截到,来进行一些自定义的返回。

  1. service worker 与主线程之间的通信
  // 传递
  navigator.serviceWorker.controller && navigator.serviceWorker.controller.postMessage("this message is from page");

  // 接收
  navigator.serviceWorker.addEventListener('message', function (e) {
    console.log('service worker传递的信息',e.data); 
  });
self.addEventListener('message', (event)=>{
  console.log('页面传递过来的数据',event.data)  // 收到主线程传递的信息
  event.source.postMessage('this message is from sw.js to page');  // 向主线程传递信息
})

  1. service worker 卸载
  navigator.serviceWorker.getRegistrations().then(function (registrations) {
    for (let registration of registrations) {
      //安装在网页的service worker不止一个,找到我们的那个并删除
      console.log(registration)
      if (registration && registration.scope === 'http://localhost:8080/') {
        registration.unregister()
      }
    }
  })
  1. serviceworker 和 http缓存

其他

会有一些开源的框架对 service worker进行了一些封装,避免了我们重复繁琐的去写一下fetch监听,install事件,active事件等,大大简化了繁琐的写法。关注度比较高的应该是谷歌推出的 workbox, 围绕他也有一系列的工具,如 workbox-cli、gulp-workbox、webpack-workbox-plagin 等等。

workbox提供了一下几种缓存策略:

总结

本次只是对于service worker 的一些基础API的尝试和学习,可以当做是一些学习资料的整理, 希望有不对的地方可以指出,service worker的知识点还有很多,有比较深入了解的同学也欢迎补充

上一篇 下一篇

猜你喜欢

热点阅读