Service Worker
Service worker 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。
这个 API 旨在创建有效的离线体验,可以用来做这些事情:
- 后台数据同步
- 响应来自其他源的资源请求
- 在多个页面间共享和更新数据
- 在浏览器内实现开发用的脚手架能力,如模块编译、依赖管理
- 性能优化,如预取资源
navigator.serviceWorker
Service worker 是一个注册在指定源和路径下的事件驱动的 worker。
它被设计为完全异步的,不能在其中使用同步的XHR,也不能使用同步的API如localStorage
,可能是因为无法保证返回时的数据符合开发者预期,容易造成时序问题。
由于 service worker 可以用作代理服务器,所以它可以实现中间人攻击。出于安全考虑,浏览器只允许在HTTPS页面或本地页面(localhost)注册、使用 service worker。
register
在浏览器中使用navigator.serviceWorker.register()
方法下载脚本URL并安装为一个service worker
<script>
navigator.serviceWorker.register("service-worker.js", {
scope: "./", //service worker注册范围,指定源和路径
}).then((registration) => {
//下载完成,准备开始安装
}).catch(err => {
//下载出错
})
</script>
生命周期
Service worker 是一个可以被多页面共享的worker,不同于SharedWorker
是在所有端口断开后关闭,Service worker可以在页面关闭后独立运行。
Service worker 有三种生命周期,从register
事件回调中读取:
installing
service worker脚本下载完成,开始执行脚本中的install
事件wating
service worker脚本更新下载完成,等待旧的脚本不再被页面使用,以替换旧脚本完成更新active
当前激活的 service worker
<script>
navigator.serviceWorker.register("service-worker.js", {
scope: "./",
}).then((registration) => {
let serviceWorker
if (registration.installing) {
serviceWorker = registration.installing
} else if (registration.waiting) {
serviceWorker = registration.waiting
} else if (registration.active) {
serviceWorker = registration.active
}
})
</script>
同个service worker脚本在浏览器不同页面中可能处于不同生命周期,例如:
- 在页面A安装service worker脚本,生命周期处于
installing
- 在页面B安装service worker脚本的新版本,生命周期处于
installing
- 在新页面C打开网页B,生命周期处于
waiting
- 关闭页面A,刷新页面B或C,生命周期处于
active
状态
有五种状态:
installing
、installed
开始执行install
事件回调、回调执行完成activating
、activated
开始执行activate
事件回调、回调执行完成redundant
当前 service worker 实例已冗余,已被注销
<script>
navigator.serviceWorker.register("service-worker.js", {
scope: "./",
}).then((registration) => {
let serviceWorker
if (registration.installing) {
serviceWorker = registration.installing
} else if (registration.waiting) {
serviceWorker = registration.waiting
} else if (registration.active) {
serviceWorker = registration.active
}
// 五种状态
if (serviceWorker) {
console.log(serviceWorker.state)
serviceWorker.addEventListener("statechange", (e) => {
console.log(e.target.state)
});
}
})
</script>
ready
这个属性返回一个Promise,当 serviceWorker
处于 active
时执行回调,即 state
值为 activating
或 activated
执行。
它不会失败,只会成功或者不执行回调。
<script>
navigator.serviceWorker.ready.then((registration) => {
console.log(`A service worker is active: ${registration.active}`)
})
</script>
适用于不生产 service worker,只消费 service worker 的页面。
当然也能在注册 service worker 的页面作为一种简化的API使用。
与 service worker 通信
与service worker和与shared worker通信类似,先要找到控制当前页面的service worker实例
<script>
navigator.serviceWorker.addEventListener("message", (e)=>{
console.log('serviceWorker message', e.data)
})
navigator.serviceWorker.ready.then((registration)=>{
registration.active.postMessage("I'm main page.")
})
</script>
和页面间通信一样,可以从event.source
获得消息来源回复消息
//service-worker.js
self.addEventListener('message', (event) => {
console.log('received message:', event.data)
event.source.postMessage('I\'m service worker')
})
service worker也可以主动向页面发送消息,这段代码需要在页面完成监听message
后运行
// service worker 主动向控制的已打开的所有页面发送消息
self.clients.matchAll().then(function(clients) {
clients.forEach(function(client) {
client.postMessage('The service worker just started up.');
});
});
fetch
事件
service worker 控制的页面,每次发起请求都会触发fetch
事件,service worker 可以读取请求。
service worker 可以通过事件对象上的方法 event.responseWith()
阻止浏览器的默认 fetch 操作,并自定义响应。
self.addEventListener('fetch', (event) => {
event.respondWith(
new Response('service worker') //页面上的任何HTTP(s)请求都会得到自定义的响应,内容为字符串
)
})
浏览器暴露此事件及API,为 service worker 提供了代理服务器的能力,也可以实现中间人攻击(修改、伪造响应)。