1、Service Worker简介
Service Worker 是一个运行在后台的 Worker 线程,然后它会长期运行,充当一个服务bulabulabula...
很多提到 Service Worker 的内容,都会将其作为处理缓存、处理离线问题的工具。
但因为其独立于浏览器页签执行,我们其实也可以利用它来统领各浏览器页签,使我们的页面支持一些需要跨页签的能力!
2、为何我需要跨页签
我们来想象一个场景,假设我们的网站需要弹出一个桌面的通知,这个我们可以通过 Notification 来弹出:
new Notification('tips', {
body: 'hello notification',
});
效果如下:
我们桌面的右下角出现了一个消息提醒,但只是提醒似乎并不足够。通常这类消息提醒是要可以通过点击,进入到消息来源的页面的。
但简单粗暴的重置当前页签的页面,视乎并不太友好,会打断用户进行中的一些操作!
那,新开页签怎么样呢?
似乎也不太行,这会让用户在使用的过程中,不断的产生新页签!
那能不能在弹出新页签之前判断一下,假设我们点击消息提醒是需要跳转页面a的,就判断下是否存在一个打开着页面a的页签,如果存在,就定位到该页签,如果不存在,就新开页签。
这样就不会打断用户在其他页签的操作,也不会不断的产生新的页签,但这就需要跨页签的能力!
3、跨页签能力初体验
我们先来看下页面内的代码:
<!-- 通知的跨页签操作 -->
<button id="showNotification">弹出消息通知</button>
<script>
// 启动service worker线程
navigator.serviceWorker
.register(
'http://localhost:8080/src/worker-space-1/worker-1-1.js',
{}
)
document
.getElementById('showNotification')
.addEventListener('click', () => {
// 因为一个网站可以启动多个service worker线程,我们需要找到我们用来处理消息通知的那个
navigator.serviceWorker.getRegistrations().then(workerList => {
const worker = workerList.find(
w =>
w.active.scriptURL ===
'http://localhost:8080/src/worker-space-1/worker-1-1.js'
);
// 这里想通过service worker来弹出消息通知,就需要用这种方式
worker.showNotification('Hello World');
});
});
</script>
然后我们再来看看给service worker执行的js代码
self.addEventListener('notificationclick', function (event) {
// 关闭消息通知
event.notification.close();
event.waitUntil(
// 获取浏览器相同域名下的所有页签!
self.clients
.matchAll({
includeUncontrolled: true,
type: 'window',
})
.then(function (clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
// 寻找指定页签,如果有,就打开它
// 这里我们用是否携带targetClient参数,来区分哪个是我们消息通知需要打开的页签
if (client.url.includes('targetClient') && 'focus' in client)
return client.focus();
}
// 如果没有,就打开新页签!
if (self.clients.openWindow)
return self.clients.openWindow(
'http://localhost:8080?targetClient=1'
);
})
);
});
试试看效果:
4、跨页签的通讯
service worker 还支持与页签互相通讯:
/* --------------------页签文件内-------------- */
// 页签接受service worker的消息
navigator.serviceWorker.addEventListener('message', e => {
// ...
});
// 页签给service worker发送消息
navigator.serviceWorker.getRegistrations().then(workerList => {
const worker = workerList.find(
w =>
w.active.scriptURL ===
'http://localhost:8080/src/worker-space-1/worker-1-1.js'
);
worker.active.postMessage({ type: 'chat', value });
});
/* --------------------service worker文件内-------------- */
// service worker接收页签消息
self.addEventListener('message', function (event) {
// 获取所有页签
self.clients
.matchAll({
includeUncontrolled: true,
type: 'window',
})
.then(function (clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (event.source.id === client.id) continue;
console.log(client);
// service worker给页签发消息
client.postMessage({ ...event.data, sourceId: event.source.id });
}
})
);
});
所以,我们其实可以利用service worker做一个页签间的聊天室!虽然这并没什么实际用处(不是
5、Service Worker的一些机制
5.1、事件驱动
service worker 是事件驱动的,只能通过监听事件,来对页签的相关操作做响应。
// 通讯事件
self.addEventListener('message', function (event) {
});
// 消息通知点击事件
self.addEventListener('notificationclick', function (event) {
});
5.2、作用域与更新
service worker 是用文件路径来做命名空间的:
对于相同路径的不同文件,service worker 会将其当作同一个 service worker 对其进行更新,而不是新建一个 worker 线程:
这就避免我们开多个页签时,多次执行相同 service worker 代码后,不至于启动多个 worker 线程
// 每个页签都会执行这段代码,但只会有一个 worker 线程被启用
navigator.serviceWorker
.register(
'http://localhost:8080/src/worker-space-1/worker-1-1.js',
{}
)