SharedWorker 是唯一能真正承载全局状态和逻辑的跨页通信层,BroadcastChannel 只适合做轻量通知;两者必须分层协作,不能混用或互相替代:SharedWorker 作为状态中枢维护 port 映射与业务逻辑,BroadcastChannel 仅广播三类状态信号(如 auth:changed、config:loaded、ws:ready),严禁传业务数据,且需页面主动清理端口与通道以防止内存泄漏。

SharedWorker 是唯一能真正承载全局状态和逻辑的跨页通信层,BroadcastChannel 只适合做轻量通知;两者必须分层协作,不能混用或互相替代。
SharedWorker 必须作为状态中枢,不能只当消息中转站
SharedWorker 脚本(如 sw.js)启动后应立即维护一个 Map 缓存所有连接页面的 port 与自定义 clientId 映射。页面首次连接时需生成唯一标识:
- 推荐用
crypto.randomUUID()(Chrome 91+/Firefox 97+ 支持),避免用Date.now()或Math.random()碰撞 - 页面在
port.start()后立刻发{ type: "register", clientId: "xxx" },Worker 收到才纳入路由表 - 不校验
clientId的 Worker 会把 A 页面发来的消息错发给 B 页面——这是最常见且难复现的数据错乱根源 - Worker 内部所有响应必须走
port.postMessage(),而非bc.postMessage(),否则无法定向
BroadcastChannel 只负责广播“状态变更”事件,不传业务数据
BroadcastChannel 的 name 必须硬编码、全站统一,例如 "app-state-bus"。它只用来同步以下三类信号:
-
{ type: "auth:changed", token: "xxx" }—— 登录态变更,触发所有页面刷新 header -
{ type: "config:loaded", version: "2.3.1" }—— 配置加载完成,避免各页面重复请求 -
{ type: "ws:ready" }或{ type: "ws:closed" }—— WebSocket 连接就绪/断开,由主控页发出,SharedWorker 监听后更新内部状态 - 严禁通过
bc.postMessage()发送用户消息、订单详情等业务数据——Chrome 对单次广播有约 1MB 限制,超限静默丢弃
页面关闭时必须显式清理,否则 SharedWorker 持有失效端口
每个页面在 beforeunload 或 visibilitychange 为 hidden 时,要执行两步清理:
- 调用
port.postMessage({ type: "disconnect", clientId: "xxx" }),让 Worker 从Map中删除该映射 - 调用
bc.close()关闭广播监听,否则该BroadcastChannel实例将持续占用内存 - SharedWorker 侧需监听
port.onclose事件作为兜底:即使页面崩溃未发 disconnect,Worker 也能自动清理 - 漏掉
port.close()不会导致立即报错,但 Worker 会持续向已销毁的端口尝试postMessage(),最终引发大量Failed to execute 'postMessage' on 'MessagePort'警告
降级策略必须在页面侧实现,SharedWorker 无法主动 fallback
SharedWorker 不被支持时(如 Safari
- 页面脚本中先尝试
new SharedWorker("sw.js"),捕获DOMException错误 - 失败后立即启用
BroadcastChannel+localStorage组合:用storage事件监听键值变更,模拟状态同步 - 所有原本发往
port的请求(如{ type: "get-user" }),改写为bc.postMessage()广播,并由各页面自行处理 - 注意:此时无法保证请求-响应语义,
bc是单向的,页面收到广播后只能本地读取localStorage获取数据
SharedWorker 的生命周期由页面连接数决定,而 BroadcastChannel 的消息可达性依赖同源与频道名一致性——这两个机制看似简单,但任何一处命名偏差、时机错位或清理遗漏,都会导致跨页状态不同步,且问题往往延迟暴露、难以定位。

















