JS-初识service worker线程,通过一个超简单的小例子

1,087 阅读5分钟

什么是service worker

service worker是一个独立于与浏览器主线程之外的线程,专门用于处理页面发送的请求。可对页面请求的返回结果进行储存。在满足一定条件下,遇到相同的请求,可以直接返回缓存中的内容,减少了页面获取结果的时间,提高了用户的体验。

不仅提升用户的体验,也可以在无网环境下,返回请求的内容

一个简单的小例子--实现请求内容的缓存

注册一个service worker

  1. 新建一个index.html文件
<body>
  <p>this is a simple demo</p>
</body>
  1. 在相同目录中新建service worker的sw.js文件
console.log('I am service worker');

self.addEventListener('install',()=>{
  console.log('install');
})
  1. 在html文件中,注册service worker
<script>
  navigator.serviceWorker.register('./sw.js')
    .then(registration=>{
      console.log('registe success');
    })
</script>

register的方法会返回一个解决为registration对象,它可以监听是否有新版本的service worker安装。也可以访问当前作用域中,不同状态的service worker

  1. 启动一个简单服务器,来访问刚才新建的index.html。然后看控制台,就可以看到下面截图的样子。

现在只有第一次打开才会有效

上面注册了一个service worker。在service worker 中对install事件进行了监听。

浏览器在注册sw脚本完成之后,即register方法返回的期约被解决后,service worker线程就会进入installing状态,即触发install事件

所以log的顺序为上图所示

为什么一定要通过服务器的形式来访问index.html?

因为出于安全考量,service worker只支持https的协议,或者IP地址为localhost、127.0.0.1的http协议

给service worker加上具体内容

install事件

self.addEventListener('install',()=>{
  console.log('install');
  
  self.skipWaitng();
})

多次对同意url调用register(),只有第一次会实际解析sw脚本,之后并不会做任何事情。除非sw脚本的内容有所改变。

当浏览器检测到sw脚本有所变化的时候,就会安装新的service worker线程。然而新的线程需要等到旧的线程不控制页面的时候,才会替换新线程,否则会一直处于等待状态。

可以认为当前页面已加载资源,因此service worker线程不应该被激活,否则就会加载不一致的资源

在控制台可以看见service worker的运行状态。图中很明显,有一个已激活的service worker线程,和一个等待状态的service worker线程

那有没有方法可以强制替换呢,答案是可以的

如上述代码中,执行self.skipWaitng()就可以了

install事件的触发条件是,sw脚本解析完成,且解析成功,即注册完成

activate事件

self.addEventListener('activate',()=>{
  console.log('activate');
  
  self.clients.claim();
})

当新版本的service进入激活状态,并不会马上控制客户端,即对页面的fetch请求进行拦截。除非发生了导航事件。

虽然《高程》中是这么说的,但是实践过程中,发现self.clients.claim()可加可不加

activate事件的触发条件是,service worker安装完成,进入激活中状态

fetch事件

//将请求和请求的响应存入缓存中
const putCache = (request, response)=>{
   self.caches.open('v1').then((cache)=>{
     cache.put(request, response);
   })
}

// 请求接口,并且将接口的响应数据存入缓存
const fetchAndStore = (request)=>{
   return self.fetch(request).then(res=>{
     putCache(request,res);
     return res;
   })
}

self.addEventListener('fetch',(event)=>{
   console.log('fetch', event.request.url);
  
  event.respondWith(
    fetchAndSotre(event.request)
      .catch(()=>self.caches.match(event.request))
  )
})

有几个知识点:

  1. service worker中用CacheStorage对象和cache对象缓存响应结果
  2. service worker是独立于浏览器主线程的线程,不能访问document、window等全局对象,但其有自己的全局对象--ServiceWorkerGlobalScope。可以通过self关键词对其进行访问
  3. 在fetch事件的回调函数中,event.respondWith(),其中的参数需要是解决为响应体的promise。
  4. 上面处理请求和缓存的策略是:优先以网络的最新数据为准,并将最新的响应缓存起来,以保证断网的时候,缓存中的数据是最新的

fetch事件触发条件是有任意请求就会触发,包括但不限于:fetch、css、js、html。即任意资源

上述的缓存策略只适用于实时性要求比较高的资源,并不适用于每种资源。像一些静态资源,只需要存一次就好

丰富html的内容

<link rel="stylesheet" href="./index.css">
  
<img src="./img/Capture.PNG"/>
<button id="btn">click me</button>

<script>
  const btn = document.getElementById('btn');
  btn.onclick = ()=>{
    fetch('./data.json')
      .then(res=>res.json())
      .then(console.log);
  }
</script>

html内容增加了css资源、img资源、fetch请求。

本地资源data.json的内容为:

{
  "name" : "zenos"
}

看看效果

我们再次访问index.html,页面长这个样子

而控制台会得到下面的输出

再次刷新页面会得到:

这是因为重复注册相同的sw脚本,register()并不会做任何操作

但当sw脚本内容更新的时候,register()会注册最新版本的sw

更新sw脚本的内容:

self.addEventListener('install',()=>{
  console.log('new install');
  
  self.skipWaitng();
})

再次刷新页面,会看到有新版本service worker注册:

这个时候我们模拟无网环境,再去刷新界面,发现页面还是原来的样子

我们来看看network的请求情况

可以看到,index.html、index.css、Capture.PNG都请求成功了,来源是serviceWorker。也有相同的请求失败了,但不影响。因为我们的缓存策略是,先请求网络,如果请求失败,就返回缓存中的数据。

总结:

到目前为止,我们借用了service worker实现了断网缓存。我们可以在自己的网页上试试这一功能。

service worker不仅仅有这个功能,还能更换缓存策略用以提升用户体验。

也初步了解了service worker的一些API,声明周期

下一节,会继续深入讲解service worker的API--service worker有关的对象