MessageChannel 适合 iframe 和 Worker 的点对点双向通信,需创建 channel、分发 port、调用 port.start() 启用接收;postMessage 是跨上下文唯一安全出口,须校验 targetOrigin 和 e.origin;CustomEvent 仅限单页内通信;Web Components + Shadow DOM 实现真正运行时隔离。

MessageChannel 适合 iframe 和 Worker 场景下的点对点双向通信
当组件之间无法直接引用(比如第三方 iframe 或独立 Worker),又需要稳定、低延迟的双向通信时,MessageChannel 是唯一原生支持“端口转移”的机制。它不依赖全局事件或字符串 type 判断,天然解耦。
关键操作只有三步:创建 channel → 分发一个 port 给目标上下文 → 双方各自调用 port.start() 启用接收。不调用 start() 就收不到消息,这是最容易漏掉的一步。
- iframe 接收 port 后必须保存引用并绑定
onmessage,不能只监听一次就丢弃 - Worker 中通过
e.ports[0]获取转移来的 port,不是e.data.port - 消息内容受结构化克隆限制:传
{ code: "x => x+1", lang: "js" }没问题,传function或document.body会静默失败
CustomEvent 仅限单页内轻量通信,别指望它跨 iframe
CustomEvent 的本质是 DOM 事件,只在当前 document 或指定 EventTarget 内有效。主页面 dispatch 的 editor:run 事件,iframe 里绝对收不到——这不是 bug,是浏览器隔离设计。
如果硬要用它串联多个区域(如编辑器 + 预览区 + 控制台),必须统一调度目标,且 detail 必须是 plain object:
立即学习“前端免费学习笔记(深入)”;
- 错误写法:
new CustomEvent("log", { detail: "error" })→ 接收方解构e.detail.msg报错 - 正确写法:
new CustomEvent("log", { detail: { level: "error", msg: "timeout" } }) - 监听必须早于 dispatch:在组件
connectedCallback或DOMContentLoaded后注册,不能等用户点击才加
postMessage 是跨窗口/跨 iframe 通信的唯一安全出口
只要涉及不同浏览上下文(iframe、popup、tab),postMessage 就是不可替代的选择。它不提供“订阅”语义,但胜在明确、可控、可校验。
两个致命坑必须避开:
- 发送时不设
targetOrigin(比如写成"*")→ 消息可能被中间页面劫持 - 接收时不校验
e.origin→ 任意网站都能伪造消息触发逻辑 - iframe 目标窗口获取失败常见于:跨域时
iframe.contentWindow为null,此时只能靠postMessage发,不能靠属性访问
Web Components + Shadow DOM 才算真正运行时隔离
所谓“解耦”,不是把 JS 拆成多个文件,而是切断视图与逻辑的运行时耦合路径。只有 customElements.define() 配合 this.attachShadow({ mode: "open" }) 能做到这点。
其他做法都是妥协:
- 用
data-属性或onclick声明行为 → 外部 JS 仍能通过 querySelector 修改或触发,没隔离 - 把逻辑封装进 class 但没挂载 shadowRoot → 样式和结构仍在全局 DOM,CSS 可穿透,DOM 可篡改
- 监听 document 上的 CustomEvent → 所有组件都暴露在同一个事件平面,命名冲突风险高
真正隔离的组件,连 querySelector 都跨不过 shadow boundary,这才是解耦的物理边界。



















