必须等待 navigator.serviceworker.ready 解析完成再通过 reg.active?.postmessage() 发消息;sw 侧用 self.addeventlistener('message') 监听,响应需借助 messagechannel 的 port 传递,且每次通信应新建通道。

页面怎么安全地给 Service Worker 发消息
必须等 navigator.serviceWorker.ready 解析完成,再调用 controller.postMessage(),否则大概率发不出去——因为此时 controller 可能还是 null,或 SW 还没激活。
常见错误是注册完就立刻发消息:
navigator.serviceWorker.register('/sw.js');
navigator.serviceWorker.controller.postMessage({ cmd: 'init' }); // ❌ controller 很可能为 undefined
正确做法是链式等待 ready 状态:
navigator.serviceWorker.ready.then(reg => reg.active?.postMessage({ cmd: 'init' }))- 如果 SW 处于
waiting状态(比如更新后未跳过),reg.active仍为undefined,需提前在 SW 里调用self.skipWaiting() - 消息体只能是可序列化值:
string、number、plain object、ArrayBuffer等;不能传function、DOM node、Promise或带循环引用的对象
Service Worker 怎么监听页面消息并响应
self.addEventListener('message', ...) 是唯一入口,但它不支持直接回复。你不能写 event.source.postMessage()——因为 event.source 是 Client 实例,而 Client 对象没有 postMessage() 方法。
所以「监听」和「响应」是两件事,且响应必须靠额外机制实现:
- 监听必须写在 SW 文件顶层,不能包在
install或activate事件里 - 想单对单响应,必须依赖
MessageChannel:页面传入port2,SW 从event.ports[0]拿到后调用postMessage() - 别用
self.clients.matchAll()去“广播回页面”——这不是响应,是伪双向,会发错对象、漏收、静默失败 - 避免在
message回调里做耗时操作(如未加锁的 IndexedDB 写入),否则阻塞后续消息
为什么用 MessageChannel 而不是 client.postMessage
因为 Client.postMessage() 不是标准 API,Chrome/Firefox 都不支持;而 MessageChannel 是规范定义、全平台兼容的双向通道方案。
它保证了三点:
- 请求与响应严格配对:每个
MessageChannel的port1和port2是绑定的,不会串 - 所有权明确转移:页面把
port2放进postMessage(..., [port2])的 transfer list,SW 才能合法使用event.ports[0] - 端口不可复用:一次通信一个新
MessageChannel,避免旧 port 已关闭却还在调用postMessage()导致静默丢弃
注意:HTML 文档未关闭时,Service Worker 线程也可能被系统终止,这时关联的 MessageChannel 就断了——所以别依赖长期存活的 port,每次通信都新建。
页面怎么接收 Service Worker 的回复
必须提前监听 port1.onmessage,且监听时机不能晚于 SW 发送响应的时间。最稳妥的方式是:创建 MessageChannel 后立即绑定 onmessage,再发消息。
典型错误是先发消息、后监听,或者监听写在 serviceWorker.ready 之前——SW 可能已经发完 reply,但页面还没挂上监听器。
- 监听要写成
channel.port1.onmessage = (e) => { ... },不是window.addEventListener('message', ...) - 别忽略
port1.start()(虽然现代浏览器大多自动 start,但显式调用更稳) - 收到响应后建议立刻
port1.close(),释放资源 - 如果页面刷新或关闭,
port1自动失效,无需手动清理
真正容易被忽略的是:MessageChannel 生命周期和页面/worker 线程状态强耦合,它不像 HTTP 请求那样有超时或重试——一旦失败,就得靠业务层重发逻辑兜底。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











