如何在Service Worker中判断Web应用状态及是否有标签页打开?
嘿,这个问题问到点子上了!Service Worker作为后台运行的脚本,本身没法直接感知前端页面的状态,但我们可以通过客户端通信和浏览器提供的API来实现需求。我来一步步给你讲清楚:
一、判断Web应用处于开启还是关闭状态
Service Worker没有直接的API来获取应用的全局开启/关闭状态,但我们可以通过页面和Service Worker的消息交互来维护状态:
页面侧发送状态通知
当页面加载完成(DOMContentLoaded)时,向Service Worker发送"应用开启"的消息;当页面即将卸载(beforeunload)或变为隐藏状态(visibilitychange)时,发送"应用关闭"的消息。这里推荐用visibilitychange,因为beforeunload在某些场景下可能不会触发(比如页面崩溃)。页面端代码示例:
// 页面脚本 navigator.serviceWorker.ready.then(registration => { // 页面加载完成,通知Service Worker应用开启 registration.active.postMessage({ type: 'APP_OPEN' }); // 页面变为隐藏/显示时,通知状态变更 document.addEventListener('visibilitychange', () => { const messageType = document.hidden ? 'APP_HIDE' : 'APP_SHOW'; registration.active.postMessage({ type: messageType }); }); // 页面卸载前补充通知 window.addEventListener('beforeunload', () => { registration.active.postMessage({ type: 'APP_CLOSE' }); }); });Service Worker侧维护状态
在Service Worker中监听message事件,根据页面发来的消息更新状态变量。如果需要避免Service Worker休眠后状态丢失,可以用IndexedDB做持久化存储:// Service Worker脚本 let appIsActive = false; self.addEventListener('message', (event) => { switch(event.data.type) { case 'APP_OPEN': case 'APP_SHOW': appIsActive = true; // 可选:将状态持久化到IndexedDB break; case 'APP_HIDE': case 'APP_CLOSE': // 加个延迟避免标签页切换时误判 setTimeout(() => { clients.matchAll({type: 'window'}).then(clients => { // 确认是否还有活跃的可见客户端 appIsActive = clients.some(client => !client.hidden); }); }, 1000); break; } });
二、判断是否有任何标签页处于打开状态
这个需求可以直接用浏览器提供的clients.matchAll()API来实现,它能获取当前Service Worker控制的所有客户端(标签页、窗口等):
1. 在Service Worker事件中查询
clients.matchAll()只能在Service Worker的事件回调(比如fetch、message、push)中调用,因为Service Worker休眠时无法执行代码。比如,当收到页面的查询请求时:
// Service Worker脚本 self.addEventListener('message', (event) => { if (event.data.type === 'CHECK_OPEN_TABS') { clients.matchAll({ type: 'window', // 只查询窗口类型的客户端 includeUncontrolled: false // 只查询当前Service Worker控制的客户端 }).then(clients => { // 判断是否有打开的标签页 const hasOpenTabs = clients.length > 0; // 回复页面查询结果 event.source.postMessage({ type: 'CHECK_RESULT', hasOpenTabs }); }); } });
2. 页面侧发起查询
页面可以主动发送消息给Service Worker,获取标签页状态:
// 页面脚本 navigator.serviceWorker.ready.then(registration => { registration.active.postMessage({ type: 'CHECK_OPEN_TABS' }); navigator.serviceWorker.addEventListener('message', (event) => { if (event.data.type === 'CHECK_RESULT') { console.log('是否有打开的标签页:', event.data.hasOpenTabs); } }); });
注意事项
includeUncontrolled参数:设为true时会返回同域下所有未被当前Service Worker控制的客户端,比如用户刚打开的页面还没完成Service Worker注册,可根据需求选择。- Service Worker休眠:如果长时间没有事件触发,Service Worker会进入休眠状态,此时内存中的变量会丢失。如果需要持久化状态,建议结合
IndexedDB存储客户端ID或状态。
内容的提问来源于stack exchange,提问作者Marcelo Mourão

