SharedWorker 是浏览器端实现全站统一心跳调度的唯一可行机制,通过跨标签页共享线程集中管理定时器、单点发送 ping、绑定连接生命周期、页面仅响应不干预、兼容性兜底与资源闭环释放来降低网络开销。

SharedWorker 是目前浏览器端实现全站统一心跳调度的唯一可行机制。它把心跳逻辑从每个页面中抽离出来,由一个跨标签页共享的独立线程集中管理——所有窗口共用一套定时器、一次 ping 发送、一次状态判断,从根本上避免多页面重复唤醒网络栈、重复建立 TLS 握手、重复触发 DNS 查询等高开销行为。
心跳必须由 SharedWorker 单点发起
多个页面各自运行 setInterval 会导致:同一时刻多个 TCP 数据包发出、浏览器频繁唤醒网络模块、后台标签页被强制“拉起”执行 JS。SharedWorker 解决这个问题的核心在于“去重”:
- 只在 Worker 内启动一个 setInterval(() => ws.send({type:'ping'}), 30000),间隔建议设为 25–45 秒(避开 Chrome 对后台 tab 的 30 秒冻结阈值)
- 页面不参与心跳发送,只响应 pong;收到 ping 后立即回传 {type:'pong', ts: Date.now()},不加任何业务逻辑
- Worker 收到 pong 后更新对应 port 的 lastActive 时间戳,用于后续超时判定
心跳状态需与连接生命周期深度绑定
单纯发 ping 不等于保活。真正降低唤醒开销的关键,是让心跳成为连接健康度的唯一信源,并驱动连接状态演进:
- Worker 内维护全局 ws.readyState 和最近一次成功通信时间 lastWsActivity
- 每次发送 ping 前检查:if (ws.readyState !== WebSocket.OPEN) return,避免无效发送
- 连续 2 次 ping 未收到任意 pong(或服务端未响应),则主动 ws.close() 并触发重连,不等待 onclose 回调
- 重连采用 setTimeout 指数退避(1s → 2s → 4s → … 最大 30s),防止多个存活页面同时重连造成雪崩
页面侧要配合做轻量协同,不抢夺控制权
页面不是旁观者,但角色必须克制:只负责初始化、上报状态、响应指令,不接管定时器或重连决策:
- 页面加载时生成 clientId,通过 port.postMessage({type:'init', clientId}) 注册
- 监听 beforeunload 或 pagehide,主动发 {type:'disconnect', clientId},加快 Worker 端清理速度
- 收到 Worker 主动广播的 {type:'status', state:'connecting'} 时,UI 可灰显按钮,但不自行触发重连
- 所有消息收发都走 port.postMessage,不尝试复用或缓存 WebSocket 实例
兼容性兜底与资源释放要闭环
心跳节省的是真实网络资源,但前提是机制本身不制造额外负担:
- Safari 16.4+ 以下版本不支持 SharedWorker,需降级为 BroadcastChannel + 主控页代理(主控页持连接,其余页通过广播同步状态)
- Worker 内所有 ws.onmessage / onerror 回调必须包裹 try-catch,否则未捕获异常会直接终止整个 SharedWorker 实例
- 使用 Map 存储 port 映射时,每次 port.close() 或超时清理后,务必 ports.delete(portId),防止内存泄漏
- 最后一个页面关闭后,Worker 会在 10–30 秒内自动终止,无需手动干预

















