必须用sharedworker管理websocket连接并用broadcastchannel同步状态,因sharedworker可复用单连接且支持new websocket(),而页面无法传递不可序列化的websocket对象;broadcastchannel仅广播连接事件,需固定频道名;各页面需唯一clientid实现消息隔离;safari兼容性差时应降级为broadcastchannel+localstorage方案。

不能靠页面自己建 WebSocket,必须让 SharedWorker 承担连接职责,再用 BroadcastChannel 协同状态。SharedWorker 是唯一能真正复用单条连接的方案,BroadcastChannel 则负责轻量、及时地同步连接生命周期事件。
SharedWorker 必须托管 WebSocket 实例
SharedWorker 脚本里可以直接调用 new WebSocket(),这是它区别于普通 Worker 的关键能力。页面无法把 WebSocket 对象传进去——它不可序列化,postMessage 会直接报错。所有连接逻辑(onopen、onmessage、重连、心跳)都得写在 worker 内部。
- 页面只发指令,例如:
{ type: "connect", url: "wss://api.example.com/ws" } - SharedWorker 收到后才初始化连接,并缓存 ws 实例供后续复用
- 关闭时调用 ws.close(),并清空缓存,避免残留状态
BroadcastChannel 用于跨页状态广播
BroadcastChannel 不管连接,只管“通知”。它和 SharedWorker 配合,解决“谁来当主控”“连接断了怎么通知”这类协调问题。频道名必须固定,比如 "app-ws-bus",所有页面和 SharedWorker 都用同一个名字,否则消息对不上。
- SharedWorker 连接成功后,通过
bc.postMessage({ type: "connected", id: Date.now() })广播就绪信号 - 各页面监听该频道,收到后不再尝试连接,转而向 SharedWorker 注册自己的 port
- 连接异常关闭时,SharedWorker 主动发
{ type: "ws-closed" },页面可触发重试或 UI 提示
每个页面需分配唯一 clientId 实现消息隔离
一条 WebSocket 连接被多个页面共享,但服务端下发的数据不能混发。SharedWorker 必须为每个接入页面生成唯一 clientId,并在转发消息时带上该标识。页面发送请求时也需附带 clientId,这样 Worker 才能把响应准确回传给发起方。
- 页面首次连接 SharedWorker 时,自动生成并上报
clientId: crypto.randomUUID?.() || Date.now().toString() - SharedWorker 将 port 与 clientId 映射存储,收到来自服务端的消息后按 clientID 分发
- 避免使用 localStorage 时间戳等弱标识,防止多页同时启动导致冲突
容错与降级要提前设计
SharedWorker 在 Safari 中支持较晚(iOS 16.4+/macOS 13.3+),生产环境必须有兜底。推荐以 SharedWorker 为主,BroadcastChannel + localStorage 为备选路径,而非混合使用。
- 页面加载时先尝试
new SharedWorker(),捕获异常则启用 BroadcastChannel 协调模式 - 在 BroadcastChannel 模式下,由首个页面建连,其余页面监听
"connected"信号并复用连接结果 - SharedWorker 恢复可用时,主动接管连接,旧页面自动退订,实现无感切换











