sharedworker 是协调全站长连接心跳最合适的调度中枢,需统一调度、去重、节流和状态同步;心跳逻辑必须下沉至 sharedworker 内部,用 setinterval 驱动,间隔设为 45–60 秒,主动发 ping 并监听 pong,连续两次未收到才判定失联。

SharedWorker 本身不能直接发起 WebSocket 连接,但它是协调全站长连接心跳最合适的“调度中枢”——关键不是让它发心跳,而是让它统一调度、去重、节流和状态同步,把原本每个标签页各自轮询或心跳的高频唤醒,收敛为单点低频保活。
心跳逻辑必须下沉到 SharedWorker 内部统一调度
多个标签页若各自用 setInterval(() => socket.send('ping'), 30s),不仅造成服务端冗余 pong 响应,更严重的是:页面切后台后主线程定时器会暂停或大幅降频(尤其 iOS Safari),导致心跳失效、连接被服务端误判断开。SharedWorker 不受页面前台/后台影响,只要同源页面至少有一个存活,它就持续运行。因此:
- 所有心跳发送、超时判断、重连触发都应在 SharedWorker 脚本中完成,用
setInterval或setTimeout驱动 - 心跳间隔设为 45–60 秒(比服务端 timeout 略短即可),避免过密唤醒,也留出网络抖动余量
- 不依赖
ws.onmessage自动响应 pong;必须主动发送 ping,并监听服务端返回的 pong 消息体,连续两次未收到才判定失联
用端口活跃度动态调节心跳强度
SharedWorker 可感知当前有多少页面在使用连接。当只剩一个标签页时,可维持标准心跳;当打开 5 个以上标签页,无需增加心跳频次——因为连接仍是同一个。但可以反向优化:
- 监听
self.addEventListener('connect', ...)统计活跃 port 数量 - 若 port 数 ≥ 3,可将心跳间隔微调延长至 55 秒(减少总请求量);若仅剩 1 个 port,恢复为 45 秒(提升单点可靠性)
- 页面关闭时主动发
{ type: 'disconnect' },Worker 清理 port 并重新评估活跃度,避免残留假连接
心跳与业务消息复用通道,避免额外连接开销
不要为心跳单独建一条连接或走另一条协议路径。真正的降频效果来自“复用+聚合”:
- 心跳消息采用轻量二进制帧或极简 JSON:
{"t":"h"},服务端只回{"t":"p"},无业务字段、无序列化负担 - Worker 收到服务端消息后,先检查是否为 pong;是则刷新本地 lastPong 时间戳,不转发给页面
- 业务消息(如通知、数据更新)和心跳完全共用同一 WebSocket 实例,不新增任何网络通道
兼容 Safari 时需降级为 BroadcastChannel + 主控页代理
Safari 16.4 以下不支持 SharedWorker 中的 WebSocket,此时无法让 Worker 主动发心跳。可行替代方案是:
- 指定一个“主控页”(如首页或登录页)创建 WebSocket 并负责心跳
- 该页面通过
BroadcastChannel('app-heartbeat')广播心跳状态(如{ type: 'alive', ts: Date.now() }) - SharedWorker 监听该频道,确认连接有效后,才向其他页面分发业务消息;若 90 秒未收到广播,则触发 fallback 重连流程
- 所有非主控页禁用心跳逻辑,完全依赖广播信号做连接健康判断










