Shared Worker 是同源多页共享的独立线程容器,非通信骨架;需结合 IndexedDB 持久化、port.start() 启动通信、端口生命周期管理及主动重同步,方能实现跨页自愈与状态一致。

Shared Worker 本身不是“通信骨架”,而是一个可被多个同源标签页复用的独立线程容器。要构建真正跨越标签页、具备自愈能力、状态统一的通信基础,必须绕开它的内存脆弱性,用外部持久层(如 IndexedDB)+ 消息协调 + 连接生命周期管理三者协同实现。
必须满足的硬性前提
不满足以下任一条件,Shared Worker 无法启动或行为异常:
- 所有页面必须同源:协议、域名、端口完全一致(例如全为 https://app.example.com,http://localhost:3000 可行,但 http://example.com 和 https://example.com 不行)
- 脚本必须是独立文件:不能是 data URL 或内联 script,路径需可被浏览器直接 fetch(如 /workers/shared-state.js)
- HTTPS 环境强制要求:生产环境必须启用 HTTPS;开发阶段仅 localhost 允许 HTTP
- Safari 兼容需谨慎:16.4+ 才支持,且部分 iOS 版本仍存在静默失败问题
页面端通信入口:port.start() 是生死线
很多通信失效的根本原因,是漏掉这一步——Shared Worker 的 port 默认处于暂停状态,不调用 start() 就收不到任何消息:
- 创建后立即获取 port:
const worker = new SharedWorker('/workers/shared-state.js'); const port = worker.port; - 先绑定监听,再启动:
port.onmessage = e => handle(e.data); port.start(); - 若用 addEventListener,同样要在监听注册后执行
port.start() - 每次页面加载都需重新建立连接和启动 port,无需手动“重连”逻辑
Worker 内部:用 IndexedDB 代替内存变量存状态
仅靠 let state = {...} 实现跨页共享,在页面刷新、Worker 重启、浏览器休眠后会彻底丢失,且高并发下 ++ 等操作非原子,必然丢数据:
- Worker 初始化时打开数据库:
const db = await openDB('shared-state-db', 1, { upgrade: ... }); - 所有读写请求都走事务封装:例如
update(key, value)方法内部执行db.transaction(...).store.put(...) - 新页面首次连接时主动发
{ type: 'sync' },Worker 从 DB 读全量数据并port.postMessage({ type: 'synced', data }) - 关键变更(如登录态、主题、购物车)写入 DB 后,再广播给所有活跃 port,确保最终一致性
自愈机制:检测断连 + 清理失效端口 + 主动重同步
Shared Worker 不自动感知 port 关闭,残留引用会导致 postMessage() 报错,也影响状态准确性:
- 维护一个活跃 port 列表:
const ports = new Set(); self.onconnect = e => { ports.add(e.ports[0]); }; - 监听 port 关闭事件:
port.onclose = () => ports.delete(port); - 广播前过滤已关闭 port:
ports.forEach(p => { if (p.postMessage) p.postMessage(msg); }); - 定期轮询(如每 30 秒)调用
self.clients.matchAll()对比当前活跃 client,补全遗漏的断连清理 - 当检测到 DB 中状态更新但某页面未收到最新消息时,可触发单点重推(需记录每个 port 的 lastSeenVersion)











