port.start() 是启用接收而非开启通道,它使 MessagePort 开始从队列中取出并分发已到达的消息;若未调用,即使消息已发送,监听器也不会触发,消息可能积压或丢失。

port.start() 不是“开启通道”,而是“启用接收”
很多人以为 port.start() 是在建立连接或初始化通信链路,其实它只做一件事:**让 MessagePort 开始从内部消息队列中取出并分发已到达的消息**。端口一旦创建(channel.port1 或 channel.port2),通信物理通路就已存在;但若没调用 start(),即使对方已 postMessage(),你也不会触发 onmessage,消息会静默积压或被丢弃(取决于浏览器实现)。
哪些场景下必须显式调用 port.start()
以下情况不调 port.start() 就收不到消息,且控制台几乎不报错,极难排查:
- 使用
addEventListener('message', handler)而非port.onmessage = handler—— 即使绑了监听器,也得先port.start() - 在设置
onmessage之前就收到了消息(比如 Worker 启动快、主线程发得早) - 使用
SharedWorker:页面侧worker.port默认处于暂停状态,worker.port.start()缺一不可 - 跨 iframe 通信时,子页面在
message事件回调里拿到event.ports[0]后,必须立刻event.ports[0].start(),不能延后
start() 的调用时机和顺序很关键
port.start() 必须在接收方完成端口获取之后、开始监听之前执行。常见错误顺序:
- 先
postMessage(..., [port]),再在接收方写port.onmessage = ...,但忘了port.start()→ 消息丢失 - 在
self.onmessage回调里拿到event.ports[0],却把它存起来、等其他初始化做完才.start()→ 中间发来的消息全丢 - 对同一个 port 多次调用
.start()→ 无害但没必要;但漏一次,就可能永远卡住
推荐写法(Worker 示例):
self.onmessage = function(e) {
if (e.data.type === 'INIT_PORT' && e.ports?.length) {
const port = e.ports[0];
port.start(); // ✅ 第一行就调
port.onmessage = e => console.log('received:', e.data);
}
};
兼容性与隐式 start 的陷阱
现代浏览器(Chrome 80+、Firefox 70+、Safari 15.4+)在你设置 port.onmessage 后会自动隐式调用 start(),但这不是规范强制行为,也不能依赖:
- 旧版 Safari 对隐式
start()支持不稳定,尤其在SharedWorker场景 - 若你用
addEventListener,所有主流浏览器都不会自动start() - 当需要精确控制消息接收时机(例如批量处理前暂停接收),你反而要手动
port.close()或暂时不start()
所以最稳妥的做法:**只要拿到 port,不管后续用哪种监听方式,第一行就写 port.start()。** 它轻量、幂等、无副作用,能绕过 90% 的“收不到消息”问题。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











