shared worker 不能直接显示通知,仅作调度中枢:判断通知是否发送、发给谁、何时发及是否合并。需维护页面状态映射表,按可见性、权限、去重规则决策,并配合页面端权限申请与状态上报;safari 旧版本不支持时需降级处理。
shared worker 本身不能直接显示通知,也不能调用 notification.requestpermission() 或 new notification()。所有通知的权限申请和 ui 展示必须在页面主线程中完成。shared worker 的真实作用是:做本地调度中枢——判断该不该发、发给谁、什么时候发、要不要合并或跳过。
为什么重复弹窗常发生
多个标签页各自监听服务端推送或定时任务,又没协调机制,就容易出现:同一订单完成消息,在三个窗口里各弹一次;用户切后台时收到通知,切回来又弹一遍;高频事件(如每秒心跳提示)连续触发,瞬间堆出十来个通知。
根本问题不在“能不能弹”,而在“谁来决定弹不弹、弹几次、弹给哪个 tab”。
Shared Worker 中的关键调度逻辑
Shared Worker 需维护一个轻量状态映射表,记录每个页面的连接状态与能力:
- 用
Map<clientid port permission visibilitystate lastactive></clientid>管理所有已注册页面 - 每个页面首次加载时生成唯一
clientId(推荐crypto.randomUUID()),并发送{ type: "register", clientId, permission } - 监听
document.visibilitychange后主动上报可见性变化,Shared Worker 更新visibilityState - 收到新通知请求(如
{ type: "notify", id: "msg-789", title: "新消息", body: "你有1条未读" })时执行以下判断:
- 查
id是否在最近 60 秒内已触发过 —— 是则丢弃(防刷屏) - 筛选出
permission === "granted"且visibilityState === "visible"的 port - 若存在多个可见 tab,只选第一个(或按
lastActive最近者)发送,避免多窗同弹 - 若无可视 tab,暂存通知载荷,等任一 tab 切回前台时再通过
port.postMessage()推送
页面端如何配合不掉链子
每个页面要主动承担两件事:申请权限 + 响应调度指令。
- 页面加载后立即调用
Notification.requestPermission(),成功后向 Shared Worker 注册自身 - 监听 Shared Worker 的
port.onmessage,当收到{ type: "show-notification", ... }时,直接执行new Notification(...) - 监听
visibilitychange,在document.hidden === false时主动上报状态,触发 Shared Worker 补发缓存通知 - 页面关闭前可发
{ type: "unregister", clientId },Shared Worker 清除对应条目(非必须,但更健壮)
兼容性兜底不能省
Safari 在 macOS 13.3 / iOS 16.4 之前完全不支持 Shared Worker,调用 new SharedWorker() 会静默失败。必须做运行时检测:
- 构造 Shared Worker 后立即
port.start()并发一条测试消息 - 1 秒内未收到回执,视为不可用,降级为单页独立处理(加本地
localStorage时间戳去重) - 不要尝试用普通 Worker 替代 —— 它无法跨页面通信,起不到统一调度作用
不复杂但容易忽略











