什么是service worker
service worker是一个独立于与浏览器主线程之外的线程,专门用于处理页面发送的请求。可对页面请求的返回结果进行储存。在满足一定条件下,遇到相同的请求,可以直接返回缓存中的内容,减少了页面获取结果的时间,提高了用户的体验。
不仅提升用户的体验,也可以在无网环境下,返回请求的内容
一个简单的小例子--实现请求内容的缓存
注册一个service worker
- 新建一个
index.html
文件
<body>
<p>this is a simple demo</p>
</body>
- 在相同目录中新建service worker的
sw.js
文件
console.log('I am service worker');
self.addEventListener('install',()=>{
console.log('install');
})
- 在html文件中,注册service worker
<script>
navigator.serviceWorker.register('./sw.js')
.then(registration=>{
console.log('registe success');
})
</script>
register的方法会返回一个解决为registration
对象,它可以监听是否有新版本的service worker安装。也可以访问当前作用域中,不同状态的service worker
- 启动一个简单服务器,来访问刚才新建的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))
)
})
有几个知识点:
- service worker中用CacheStorage对象和cache对象缓存响应结果
- service worker是独立于浏览器主线程的线程,不能访问document、window等全局对象,但其有自己的全局对象--ServiceWorkerGlobalScope。可以通过self关键词对其进行访问
- 在fetch事件的回调函数中,event.respondWith(),其中的参数需要是解决为响应体的promise。
- 上面处理请求和缓存的策略是:优先以网络的最新数据为准,并将最新的响应缓存起来,以保证断网的时候,缓存中的数据是最新的
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有关的对象