前言
长期更新请看我的个人博客,因为我没法在那么多的平台同步更新 https://blog.imlete.cn/article/Service-Worker-Preferred-Request-Resource.html
当你的网站或博客有多个部署点时,部署在某个平台的访问速度比较快,于是你就把你的域名解析到了这个平台上,但有时候还是会变得很慢,这时其它站点速度可能会变得比你当前使用的还快一点,难道还有来回解析域名吗?太麻烦了
有没有可以直接返回最快网站资源的办法呢?
- 使用域名管理平台,有些平台可以解析不同网络或地区的站点
例如腾讯云可以区分解析国内三大运营商、境内、境外、等一些解析选项(不太好用,还需要自己测试,难不成求使用其它运营商手机的朋友帮你测一下快不快嘛~) - 使用 js 拦截网站的所有请求,并篡改将请求发送到自己的所有站点,这些站点中如果哪个站点最快返回,那么就用最快返回的这个信息,与此同时将其它的请求全部切断
正文
本文会详细说明如何使用 Service Worker 优选请求资源让你的网站比以前更快,更稳定
Service Worker在接下来的内容中统一称呼为sw
Service Worker
更详细请看https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API
Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器
Service worker 运行在 worker 上下文,因此它不能访问 DOM。相对于驱动应用的主 JavaScript 线程,它运行在其他线程中,所以不会造成阻塞。
出于安全考量,Service workers 只能由 HTTPS 承载,毕竟修改网络请求的能力暴露给中间人攻击会非常危险。
注册
注册 sw 很简单,只需一行代码即可,如果注册成功,则 sw 会被下载到客户端并且安装和激活,这一步仅仅是注册而已,完整的是: 下载—>安装—>激活
注意: sw 的注册日志记录在 Chrome 浏览器中可以通过访问chrome://serviceworker-internals查看
navigator.serviceWorker.register('/Service-Worker.js')
其中/Service-Worker.js
必须是当前域下的 js 文件,他不能是其它域下的,即使 js 文件内的内容完全相等,那也不行
如果你只想在某个路径写使用 sw 的话,你可以使用scope
选项,当然/Service-Worker.js
的位置也可以自定义(只要是同源且是 https 协议就可以),如下只有在/article/
文章页 sw 才启动,其它路径写 sw 不进行处理
navigator.serviceWorker.register('/sw-test/Service-Worker.js', {
scope: '/article/' })
并且必须是 https 协议,如果是本地127.0.0.1
或localhost
是被允许的
这是一个完整的注册代码
将安装代码放置在<head>
之后
<script>
;(function () {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then((result) => {
// 判断是否安装了sw
if (!localStorage.getItem('installSW')) {
localStorage.setItem('installSW', true)
// 这里就不用清理setInterval了,因为页面刷新后就没有了
setInterval(() => {
// 判断sw安装后,是否处于激活状态,激活后刷新页面
if (result && result.active && result.active.state === 'activated') {
location.reload()
}
}, 100)
}
})
.catch((err) => {
console.log(err)
})
}
})()
</script>
生命周期
installing 状态
当注册成功后会触发install事件,然后触发activate事件,此时如果再次刷新页面,它俩都不会被触发了
直到/sw.js
发生了改变,它就会触发一次 install (不仅仅是代码改变,哪怕是多一个空格或是少一个空格,又或是写一个注释都会被触发),但是只执行了install事件,并没有执行activate事件
activing 状态
为什么activate事件不触发了?因为已经有一个 sw 了,它一种处于等待状态,至于什么时候才会被触发,那就是等之前的 sw 停止了才会触发activate事件
那有没有办法不让它等待呢?答案是: 有
使用skipWaiting()
跳过等待,它返回一个 promise 对象(异步的),防止还在执行skipWaiting()
的时候直接就跳到activate事件,我们需要使用async/await
,也可以使用event.waitUntil(skipWaiting())
方法把skipWaiting()
放到里面,和async/await
效果一样
// sw.js
// 在sw中可以使用this或是self表示自身
self.addEventListener('install', async (event) => {
// event.waitUntil(self.skipWaiting())
await self.skipWaiting()
})
触发activate事件后 ,当前这一次网页是不会被 sw 管理的,需要下次页面刷新才会被 sw 管理,那怎么让它立即管理页面呢?
更详细请看:
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting
https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim