SharedWorker是唯一能跨标签页统一维护WebSocket心跳的机制,它将心跳逻辑完全下沉至独立共享线程,通过setInterval定时发送ping、服务端响应pong闭环验证、连续两次超时未响应则主动断连重连,并广播状态供页面监听,从而实现全站连接保活与资源优化。

Shared Worker 不适合用来发送“一次性”保活心跳——它设计的初衷是长期、稳定、跨标签页统一维护连接健康度,而不是执行单次任务。所谓“一次性心跳”,本质是误用:既无法保证送达(页面可能秒关),又失去 Shared Worker 的核心价值(状态持续、多端协同)。真正该做的是建立一个由 Shared Worker 全权托管的**可持续心跳机制**,让所有标签页共享同一个保活出口。
心跳必须在 Worker 内持续运行,不能靠页面触发
页面加载后调用 new SharedWorker() 只是起点,后续所有保活逻辑必须完全下沉:
- 在 Shared Worker 脚本中直接使用
setInterval(() => ws.send({type: "ping", ts: Date.now()}), 30000)启动固定节奏,不依赖任何页面传入的参数或时间戳 - 禁止页面通过
postMessage发送 “请发一次心跳” 类指令——这类消息可能丢失、重复、或在 Worker 尚未 ready 时发出 - 页面切后台、崩溃、刷新时,Worker 内的定时器照常运行,这才是它不可替代的关键
服务端响应必须闭环,否则“心跳”形同虚设
只发 ping 不收 pong,等于没心跳。Worker 必须自主完成探测闭环:
- 每次发 ping 前记录发送时间戳;收到服务端返回的
{type: "pong", ts: ...}后,校验ts是否匹配(或至少存在),并更新本地lastPongTime - 连续两次 ping 未在 45 秒内收到有效 pong,就主动
ws.close()并启动重连,不等待onclose或超时自动断开 - 服务端若不支持自定义 pong,至少需透传标准 WebSocket Pong 帧(RFC 6455),Worker 可监听
ws.onmessage中的event.data === ""(空 payload)作为基础确认
多标签页不是“多个客户端”,而是一个逻辑单元
所有标签页共用一条 WebSocket 连接,Shared Worker 是唯一协调者:
- 页面首次接入时发送
{type: "init", clientId: "tab-abc"},Worker 将其与port绑定,用于后续消息路由和状态广播 - 页面关闭前(
beforeunload或pagehide)主动发{type: "disconnect", clientId},Worker 清理映射,但不关闭连接——其他标签页仍在使用 - 状态变更(如
"connecting"、"connected"、"disconnected")统一由 Worker 通过port.postMessage()广播,页面只监听、不轮询、不干预
兼容性兜底不能缺,尤其对 Safari
iOS Safari 16.3 及更早版本不支持 Shared Worker,必须检测并降级:
- 创建前检查
typeof SharedWorker !== "undefined",不可用时 fallback 到BroadcastChannel+ 主控页模式 - 主控页(如首页)承担心跳职责,通过
broadcastChannel.postMessage({type: "heartbeat-ok", ts: Date.now()})向其他页面广播存活信号 - 非主控页只监听不发送,UI 根据最后收到的时间戳判断是否“疑似离线”,避免误判
不复杂但容易忽略:心跳不是“发一次让连接不断”,而是“持续验证连接是否还有意义”。Shared Worker 的价值,正在于把这件事从每个页面的偶然行为,变成全站确定性的基础设施。

















