Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,它会在浏览器启动后运行
用处
- 缓存/预存
api
、静态资源
- web离线,充当服务器
- 拦截请求
- 消息推送
使用限制
- 兼容性
- 只可以用于
https
域名下(本地也可以) - 不可操作dom
- 不可使用localStorage
兼容
serviceWorker被置于navigator
对象上,判断其存在则可以使用
if (navigator.serviceWorker) {
....
}
注册
serviceWorker.register(path, {scope: './'})
-
path
:serviceWorker脚本路径 -
scope
:指当前serviceWorker的作用范围,上面是用于整个网站
返回值一个promise对象,成功得到ServiceWorkerRegistration对象
navigator.serviceWorker.register('server.js', { scope: './' }).
then((reg) => {
console.log(reg)
}).catch((err) => {
console.log(err)
})
注:
- 如果
scope
是根目录,那么server.js
必须在根目录 - 同源下可以注册多个sw,但是
scope
必须不同
下载
注册成功后进入脚本下载,之后刷新或者自动在24小时内会重新下载脚本,检查脚本是否更新
安装
下载或更新脚本后会进行安装,触发install
事件,完成后执行event.waitUntil(promise)
脚本中使用this/self
直接访问相关接口
// server.js
self.addEventListener('install', function (event) {
console.log('Service Worker install');
});
激活
第一次安装成功后或新脚本可用后,会进行激活,触发activate
事件
这个事件主要用于清理旧缓存和旧的service worker的一些东西
self.addEventListener('activate', function(event) {
var cacheWhitelist = ['v2'];
event.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (cacheWhitelist.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
);
});
注:
- 更换脚本后,新脚本会在后台安装,等待旧脚本不再被页面使用才会激活新脚本,刷新旧脚本依然会使用,只有完全退出,然后重新进入网站才会正式使用
可以使用skipWaiting
跳过等待,并立马控制页面
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
// ...
);
});
skipWaiting()意味着新 SW 控制了之前用旧 SW 获取的页面,也就是说你的页面有一部分资源是通过旧 SW 获取,剩下一部分是通过新 SW 获取的,如果这样做会给你带来麻烦,那就不要用skipWaiting()
- 初次激活后(包括第一次安装激活),当前作用域内的页面并未受控(意味着,fetch拦截等都不生效),需要等到刷新或者使用
clients.claim()
来让页面立马受到控制
self.addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim());
});
请求拦截
使用fetch
事件可以监听http请求,也可以使用respondWith(promise)
方法或 Response 对象自定义响应内容
this.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request) // 从缓存中读取直接返回
);
});
缓存
sw使用CacheStroage存储请求,接口为 caches
-
caches.open('xx')
打开缓存库xx(无则新建),返回promise,打开后才可进行后续cache操作 -
caches.delete(xx)
删除缓存库xx(超过限制,浏览器会自动删除) -
caches.match(request)
从缓存中查找请求,返回promise,成功拿到响应数据 -
cache.put(request, responeseClone)
缓存一个请求,存储响应应该是复制的responese.clone()
(因为一个请求和响应流只能被读取一次) - 其他api
一个缓存所有请求的例子:
this.addEventListener('fetch', function (event) {
event.respondWith( // 拦截响应
caches.match(event.request).then(res => { // 从缓存中查找,有则返回,无则请求并存储
return res ||
fetch(event.request)
.then(responese => {
const responeseClone = responese.clone(); // 复制一份响应数据
caches.open('xx').then(cache => { // 打开存储库 xx
cache.put(event.request, responeseClone); // 请求响应存入 xx
})
return responese;
})
.catch(err => { // 缓存读取失败,请求也失败,应该在这里做兼容处理,比如显示加载失败检查网络
console.log(err);
});
})
)
});
// 作者:十月七秋
// 链接:https://juejin.im/post/5b06a7b3f265da0dd8567513
通信
页面到sw
- 页面发送
serviceWorker.controller.postMessage(msg) 进行信息发送
// sw 注册后才能发送
navigator.serviceWorker.controller.postMessage("this message is from page");
- sw接收
监听message
事件获取发送的信息
this.addEventListener('message', function (event) {
console.log(event.data); // this message is from page
});
如果注册的scope
不是根路径,使用sw实例.active.postMessage
navigator.serviceWorker.register('./sw.js', { scope: './sw' })
.then(function (reg) {
reg.active.postMessage("this message is from page, to sw");
})
sw到页面
- sw发送
在message
事件中发送
this.addEventListener('message', function (event) {
event.source.postMessage('this message is from sw.js, to page');
});
- 页面接收
接收都使用message
事件
navigator.serviceWorker.addEventListener('message', function (e) {
console.log(e.data); // this message is from sw.js, to page
});
sw 到 sw
Message Channel
https://juejin.im/post/5b06a7b3f265da0dd8567513#heading-5
插件
https://developers.google.com/web/tools/workbox
https://zoumiaojiang.com/article/amazing-workbox-3/
https://juejin.im/post/5d47f5c45188255d2a78af38#heading-6
https://lavas.baidu.com/guide
参考资料
https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting
MDN Service Worker
MDN SW 实例
Service Worker最佳实践
Service Worker 全面进阶
网站渐进式增强体验(PWA)改造:Service Worker 应用详解
Service Worker —这应该是一个挺全面的整理