尧图精选

iframe跨域通信完全指南:postMessage原理、实战与避坑

🕒 发布时间:2026/10/2 11:15:33 📁 来源:尧图网络
1. 为什么你迟早会和 iframe 跨域通信打交道先说个场景你辛辛苦苦搭了一个后台管理系统某天业务方提了个需求——要把另一个团队开发的报表页面、数据看板或者第三方工具直接嵌到你的系统里。你一听这简单iframe srcxxx一行代码搞定。结果嵌进去之后发现子页面想告诉父页面“用户点了保存”父页面想通知子页面“刷新一下数据”因为你用的是原生 DOM 操作或者普通的事件绑定压根拿不到另一个窗口的引用控制台还飘着一行跨域报错。这就是 iframe 跨域消息通信要解决的问题。只要你的页面里有 iframe、embed、object、window.open 打开的窗口并且这些窗口跟主页面不在同一个源协议、域名、端口任意一个不同你就绕不开 postMessage 和 message 事件这套方案。它是浏览器官方提供的、专门用于跨窗口、跨域传递数据的 API比 JSONP、CORS 修改后端、代理转发那些方案更轻量也更贴合“前端页面之间直接对话”的场景。我做过的后台项目里至少有三四次是死在“怎么让 iframe 内外互相通信”这个问题上早期还试过改 document.domain、轮询 localStorage、window.name 各种土办法最后都用 postMessage 重写才彻底解决。这篇就把它彻底讲透从原理到写法从父子双向通信到多窗口广播再到真实的坑和排查思路你能想到的场景基本都覆盖了。适合谁看前端开发、需要做集成页面的全栈工程师、做低代码平台或多系统聚合门户的同学都值得收藏一份。2. 先搞清楚 iframe 跨域到底卡在哪一步2.1 同源策略浏览器不给你的东西要理解跨域消息通信必须先理解一个前提浏览器有同源策略Same-Origin Policy。它的意思是来自同一个协议、域名、端口的页面之间浏览器才允许相互访问 DOM、Cookie、localStorage 这些东西。比如https://admin.example.com和https://api.example.com域名不同算跨域http://localhost:8080和http://localhost:9090端口不同也算跨域。同源策略限制的是“页面脚本对另一个窗口内部内容的访问能力”。你可以把 iframe 想象成一个玻璃房子你能看到里面的人在动页面渲染出来了但你伸手进去摸他的桌子操作子页面 DOM就会被玻璃挡住这层玻璃就是浏览器安全策略。所以在跨域 iframe 里以下操作基本都会被浏览器拦下来父页面执行iframe.contentDocument.getElementById(...)去读子页面节点子页面直接访问window.parent.document去改父页面内容直接用window.parent.xxFunction()调用对方的全局函数我见过很多刚接手这类需求的同学第一反应就是“我直接调对面页面的函数不就行了吗”。不行浏览器不允许。跨域之后你手里能拿到的只有对方窗口的window引用但里面的属性和方法全部被锁死你唯一能安全使用的“通道”就是 postMessage。2.2 跨域场景有哪些实际项目里iframe 跨域通信的形态大概有这几种场景父页面子页面是否能直接用 DOM 互访同域父子a.example.coma.example.com/child可以子域不同a.example.comb.example.com不行不同域名example.comanother.com不行协议不同https://a.comhttp://a.com不行本地调试localhost:8080localhost:3000不行后面四种场景正常手段都走不通postMessage 就是为它们准备的。2.3 CORS 和 postMessage 不是一回事很多人会混淆我后端都配了 CORS 跨域为什么 iframe 还是通信不了CORS 控制的是“浏览器向跨域服务器发起 AJAX 请求时服务器允不允许这个请求被读取”。它解决的是页面和服务器之间的资源访问问题。postMessage 解决的是“两个独立窗口/文档之间的数据传递”问题。后端配了 CORSifrmae 页面照样不能直接操作父页面 DOM因为这不是 HTTP 请求层面的问题而是窗口间的安全隔离。理解这一点你排查的时候就不会跑偏。3. postMessage 和 message 事件核心机制拆解3.1 postMessage 到底发了什么、发给谁postMessage 的调用方式是目标窗口.postMessage(message, targetOrigin, transfer)注意是“目标窗口”调不是随便哪个窗口调。这里是最容易绕晕的地方。举个例子父页面要发给子页面代码是// parent 页面里 const iframe document.getElementById(myIframe); iframe.contentWindow.postMessage(hello child, https://child.example.com);父页面拿到的是子页面窗口的引用iframe.contentWindow然后在这个引用上调用 postMessage消息才会发进子页面里。反过来子页面要发给父页面代码是// child 页面里 window.parent.postMessage(hello parent, https://parent.example.com);子页面拿到父页面窗口的引用window.parent同样在它上面调用 postMessage。第二个参数targetOrigin很关键它用来指定“这个消息只允许发送给哪个源”。如果目标窗口当前的源和你指定的源不匹配浏览器会直接丢弃这条消息不会真的发过去。如果你不关心目标窗口是什么源可以写成*但安全上不建议这么干后面会细说。第三个参数transfer比较少见它用于转移可转移对象比如 ArrayBuffer 的所有权多数场景用不上知道有这个东西就行。3.2 message 事件消息到了怎么接收发送只是第一步接收的一方要靠监听message事件才能拿到数据。不管是父页面还是子页面接收的代码长一样window.addEventListener(message, function(event) { console.log(event.data); console.log(event.origin); console.log(event.source); });event对象上有几个属性你必须烂熟于心event.data发送方传过来的数据可以是字符串、对象、数组甚至通过 structured clone 算法复制过来的复杂结构event.origin发送方页面的源格式是“协议 域名 端口”这是你判断消息是否可信的核心依据event.source发送方窗口的引用如果要做回执、要回复消息就靠它event.lastEventId和服务端消息事件相关的属性前端场景里很少用很多人刚学时最大的困惑是我在子页面里写的监听浏览器怎么知道要把消息分给谁答案是所有窗口都会收到 postMessage 派发的 message 事件但你监听了、且消息来源合法事件才在你的监听器里被处理。所以每个窗口里只管写好自己的监听逻辑即可。3.3 为什么必须校验 event.origin这是新手最容易忽略、老手最容易翻车的点。试想一下你如果不在接收端校验event.origin任何网站都能往你的页面窗口里发消息。攻击者可以构造一个恶意页面把你的页面嵌在 iframe 里然后向外发一条伪造的 message你的监听器接到后可能会执行一些危险操作——比如把用户数据发出去、改变页面状态、触发某个接口调用。所以接收端的标准姿势是window.addEventListener(message, function(event) { if (event.origin ! https://parent.example.com) { return; } // 安全了再处理数据 handleMessage(event.data); });白名单里可以是一个数组当存在多个可信来源时逐个判断。这条习惯必须养成我见过不止一次因为少写一个 origin 校验测试环境一切正常上线后被第三方恶意投递消息的案例。4. 完整实操父子窗口双向通信的落地写法4.1 父页面发送消息给子页面假设父页面是https://parent.example.com/admin.html子页面是https://child.example.com/widget.html父页面嵌了一个 iframe要在用户点按钮时通知子页面“刷新报表数据”。父页面代码!-- parent.html -- iframe idreportFrame srchttps://child.example.com/widget.html/iframe button idrefreshBtn刷新子页面报表/button script const iframe document.getElementById(reportFrame); document.getElementById(refreshBtn).addEventListener(click, function() { iframe.contentWindow.postMessage({ type: REFRESH_REPORT, payload: { force: true } }, https://child.example.com); }); /script子页面代码// widget.html 里 window.addEventListener(message, function(event) { if (event.origin ! https://parent.example.com) { return; } const msg event.data; if (msg msg.type REFRESH_REPORT) { loadReportData(msg.payload.force); } });有两个细节需要注意。第一我特意把消息做成了对象结构{ type, payload }而不是传裸字符串这样后续要扩展消息类型时不用改通信协议加一个 type 值就行。第二子页面必须在window上监听不是 document 上这两个地方容易混但规范里 postMessage 的事件是分发给窗口对象的。4.2 子页面回传消息给父页面子页面操作完成后需要告诉父页面“报表刷新成功共拿到 200 条数据”。子页面这样发window.parent.postMessage({ type: REFRESH_DONE, payload: { count: 200 } }, https://parent.example.com);父页面这样接window.addEventListener(message, function(event) { if (event.origin ! https://child.example.com) { return; } const msg event.data; if (msg msg.type REFRESH_DONE) { updateStatus(刷新成功共 msg.payload.count 条); } });到这里一个最基础的双向通信闭环就走通了父通过iframe.contentWindow.postMessage发给子子通过window.parent.postMessage发回给父两边各自在window上监听 message 事件并严格校验event.origin。4.3 页面加载时序iframe 没加载完怎么办这是“为什么我发了消息子页面没反应”排行榜第一的原因。页面加载是有时序的。你在父页面 DOMContentLoaded 时立刻调iframe.contentWindow.postMessage可能子页面的脚本压根还没执行到挂监听那一步消息发过去就石沉大海了。解决思路一般有三种第一种父页面等 iframe 的 load 事件再发。用iframe.onload function() { ... }来保证子页面资源加载完毕。但注意load 只保证资源加载完不代表子页面的 JS 监听已经执行严格说还不完全可靠。第二种双方约定握手协议。子页面加载完主动给父页面发一条CHILD_READY消息父页面收到后再发正式业务消息。这是我认为最稳妥的方案因为子页面知道自己什么时候才真正准备好接收指令。第三种父页面用setTimeout延迟发送。这个只能用于临时调试生产环境千万别依赖猜时间。我实际业务里一直都是用握手协议子页面挂好监听后立刻向父页面发送 READY 标志父页面维护一个状态位收到 READY 后才允许用户触发业务消息。4.4 子页面刷新父页面数据反向数据流设计还有一种常见场景子页面里做了增删改操作要刷新父页面的列表。同样的原理子页面发DATA_CHANGED消息父页面收到后重新请求自己的接口数据。整个思路和 4.2 一模一样只是消息语义不同。这里想多说一句设计上的建议用 postMessage 通信时最好在项目里把消息类型集中定义在一个常量文件里共用避免魔法字符串满天飞。比如单独建一个messageTypes.js里面导出REFRESH_REPORT、REFRESH_DONE、DATA_CHANGED这些常量。两边项目是不同仓库的话可以抽成 npm 包或者直接复制一份同名常量文件否则消息类型写错一个字母调试起来很痛苦。5. 复杂场景多 iframe 广播、回执确认、跨域传参5.1 父页面给多个 iframe 广播消息一个页面里嵌了三个不同业务线的 iframe父页面想一次性通知它们“用户登录状态变了”或者“全局主题切换了”。每个 iframe 的引用不一样那就循环遍历const frames [ document.getElementById(frame1), document.getElementById(frame2), document.getElementById(frame3) ]; frames.forEach(frame { frame.contentWindow.postMessage({ type: GLOBAL_THEME_CHANGE, payload: { theme: dark } }, *); });这里 targetOrigin 用了*可能有人会问这样子安全吗关于targetOrigin用*的安全性要分场景。往前看*意味着不管目标窗口当前处于什么源浏览器都会尝试把消息发过去。如果这些 iframe 都是同公司的页面且内容可信用*在功能上没问题。但是如果你能确定每个 frame 的确切源还是建议分别指定准确 origin多写几行代码换来的是更可控的消息投递。另外有个细节如果父页面嵌了多个 iframe且这些 iframe 同源那用document.querySelectorAll(iframe)遍历会更灵活但要防止拿到隐藏的无关注入组件。5.2 子页面如何拿到父页面的准确身份子页面接收消息时event.origin给的是发送方“当时的源”。如果父页面在开发环境是localhost:8080测试环境是test.example.com生产环境是example.com那子页面的白名单就得配置多个来源。一条不够严谨的错误做法是直接event.origin.includes(example.com)因为fakeexample.com也能通过 includes 判断必须精确匹配完整源字符串除非你对源的结构做了专门解析。5.3 需要子页面处理完再回执怎么办有些场景是父页面发了一个任务要求子页面处理完再回传结果类似 RPC 调用的语义。只靠单个 message 事件父页面不知道该结果对应哪一次请求。这时候可以给每条消息加一个自增的requestId// parent 侧 let reqId 0; const pendingMap new Map(); function sendToChild(payload) { reqId; const id req_ reqId; pendingMap.set(id, { resolve: null, timer: null }); return new Promise((resolve, reject) { pendingMap.get(id).resolve resolve; pendingMap.get(id).timer setTimeout(() { pendingMap.delete(id); reject(new Error(child 处理超时)); }, 10000); iframe.contentWindow.postMessage({ type: CALL_CHILD, requestId: id, payload }, https://child.example.com); }); } // 监听子页面回执 window.addEventListener(message, function(event) { if (event.origin ! https://child.example.com) return; const msg event.data; if (msg msg.type CALL_CHILD_RESULT) { const pending pendingMap.get(msg.requestId); if (pending) { clearTimeout(pending.timer); pending.resolve(msg.payload); pendingMap.delete(msg.requestId); } } });子页面处理完数据后把同一个requestId原样带回。父页面用 Promise 包裹整个过程调用的地方直接await sendToChild({ action: getDetail })就能拿到结果代码可读性非常高。这套模式我建议在涉及“父子协同完成一个业务操作”的场景里直接抄。5.4 多级嵌套 iframe 怎么通信有时候页面层级不止两层最外层页面 A 嵌了 BB 又嵌了 C。A 想给 C 发消息怎么办直接发不行因为 A 拿不到 C 的 window 引用。处理办法是逐层转发。A 把消息发给 BB 收到后再转给 CC 的响应也逐层往上传递。中转页面的逻辑很简单收到消息后判断目标层级决定是自己消费还是继续转发。这种链路里要注意消息头加一个targetLevel或targetFrameId字段避免每一层都误以为消息是发给自己的。另外层级越深延迟和出错概率越高能用两层解决的不要设计成三层。5.5 和 Vue3 项目嵌套 iframe 的搭配实践热搜词里有vue3嵌套iframe这确实是高频场景。Vue3 组件里操作 iframe 有个经典坑如果v-if控制 iframe 的渲染组件卸载再重新挂载后旧的 iframe 引用就失效了必须重新获取新引用。而且 iframe 的src如果是在:src上动态绑定的页面加载完成后才能拿到contentWindow。实际开发里我通常会把 iframe 通信逻辑封装成一个 composable 或者一个工具模块里面统一管理 iframe 引用、监听函数注册、销毁时移除监听而不是散落在各个组件的生命周期函数里。组件卸载时记得onBeforeUnmount(() { window.removeEventListener(message, handler); });不然你切了几次路由就挂了 N 个监听器在 window 上消息会被处理好几遍出现“明明只发一次子页面却刷新了三次”的诡异问题。5.6 主页面直接调用 iframe 里的函数到底行不行这个问题在热搜里也很常见主页面可以调用iframe的函数吗。如果同域理论上可以iframe.contentWindow.someFunction()就行。但一旦跨域对不起调用会被浏览器拦截。这也是为什么大家最终都会落到 postMessage 方案上。换一种思路你想调用的“函数”本质上是一个操作指令。在跨域场景下你不需要真的拿到对方的函数引用只需要发送一个消息告诉对方“请执行某个操作”对方收到消息后自己调用自己的函数即可。理解这个换位思考跨域通信就没什么神秘的了。6. targetOrigin、安全校验与前端防御清单6.1 targetOrigin 传*和具体 origin 的区别targetOrigin是投递阶段的校验浏览器只把消息发给当前源匹配的窗口。如果你写了具体源且目标窗口当前源不匹配消息直接被丢弃这是一种很有价值的前置保护。但要注意一点我们发送消息时targetOrigin写的是“发送方期望的目标源”。如果目标窗口在 iframe 内部发生了跳转比如你嵌的是https://child.example.com但用户在里面点了一个链接跳到https://evil.example.com这时候你依然拿着旧的contentWindow引用去 postMessage并指定targetOrigin为https://child.example.com浏览器发现目标窗口已经不是这个源了消息就不会被送达。这其实是保护机制在起作用避免你的数据被恶意页面接收。建议凡是能明确目标源的场景都不要用*。*只在“广播给多个不同源窗口”且数据不敏感的情况下使用。6.2 接收端校验清单接收端必须检查以下内容缺一不可event.origin是否在白名单内event.data是否满足期望的结构比如必须是一个对象且有 type 字段如果消息会触发接口调用或 DOM 操作最好再校验一下业务参数是否合法我有一个习惯统一封装一个isTrustedMessage(event, allowedOrigins)函数所有监听器都先过这个函数不符合直接return。这样即使以后新加监听器也不会忘记校验 origin。const ALLOWED_ORIGINS [ https://parent.example.com, https://child.example.com ]; function isTrustedMessage(event) { return ALLOWED_ORIGINS.includes(event.origin); } window.addEventListener(message, function(event) { if (!isTrustedMessage(event)) return; // 业务处理 });6.3 消息内容千万别直接用来拼 HTML即使你校验了 origin也不能保证消息内容是安全的。对方页面如果被 XSS 注入它发出的消息可能就是恶意构造的。所以在父页面收到消息后如果消息里的 payload 要插入到 DOM一定要用textContent而不是innerHTML或者至少经过转义处理。把 postMessage 收到的数据一律视作不可信输入这条原则能帮你避开大部分安全漏洞。7. 真实项目中的坑排查思路与高频问题实录7.1 消息发了对方就是收不到按顺序排查检查发送方调用的窗口引用对不对。父发子要用iframe.contentWindow.postMessage子发父要用window.parent.postMessage反过来写全都白搭。检查监听器是不是挂在 window 上且已经执行到挂载代码。子页面脚本还没执行完就监听失败的情况很常见尤其是把 script 写在了页面底部而 iframe 又加载太快时。检查 targetOrigin 是不是写错了。如果写的是完整源而对方窗口当前源有细微差别比如多了 www、端口没写消息会被丢弃。打开控制台在监听器里第一步就console.log(event.origin, event.data)先确认消息是否真的到达了。7.2 监听器触发了多次大概率是你在多个地方或者同一个地方反复调用都注册了同一个 message 监听器比如组件 created 里注册了又在 mounted 里注册了一次或者路由切换没有移除监听。处理方式用具名函数而不是匿名函数保证removeEventListener能精准移除组件销毁时一定要移除。7.3 子页面 received 到了但父页面监听不到子页面的回复子页面发消息时用的窗口引用写反了或者targetOrigin和父页面当前源不一致。本地开发时父页面是http://localhost:8080子页面发送方 targetOrigin 却写了http://127.0.0.1:8080两个源看起来一样实际上不匹配消息被丢弃。这种“看起来一样”的坑最磨人排查时把两端实际跑在哪个源打出来对比。7.4 vue3 里 iframe 的 v-if 切换导致消息丢失如果在 Vue3 中用了v-ifshowFrame来动态渲染 iframe每次切换后 iframe 都是新创建的旧的内容已经被销毁。消息发出去了目标窗口已经不存在了。解决不要缓存旧引用每次创建 iframe 后用回调或 watch 等新引用稳定后再发消息更稳的做法还是让子页面主动发送 READY 消息父页面只向“已就绪的 iframe”发消息。7.5 iframe 加载的第三方页面不允许被嵌入热搜里提到的 dataease 社区版明确禁止通过 iframe 嵌入这种问题不是 postMessage 能解决的而是对方页面通过X-Frame-Options响应头值是 DENY 或 SAMEORIGIN或者 CSP 的frame-ancestors指令拒绝被嵌。你的 iframe 会直接显示一个空白页或者浏览器的“拒绝连接”页面。遇到这种限制要么联系对方开放白名单要么在后端用代理转发方式拿数据再自己渲染这已经脱离 iframe 通信的范畴属于数据聚合思路了。排查方法很简单打开控制台看 Network 面板找到 iframe 对应文档的响应头确认有没有X-Frame-Options或Content-Security-Policy限制。7.6 同域 iframe 反而“通信不了”同域时理论上直接用 contentWindow 调函数就行但我在项目里也见过有人同域还非要走 postMessage结果因为过度封装反而出问题。同域场景下postMessage 当然也可以用浏览器允许你这么做只是没必要。直接调函数、直接操作 DOM 效率更高也更直观。要判断“到底是否同域”在控制台跑一下window.location.origin和iframe.contentWindow.location.origin一样就是同域。7.7 iframe 隐藏滚动条和通信的关系有人问 iframe 隐藏滚动条跟通信有什么关系本身没关系但如果你操作的是父页面上 iframe 标签的样式scrollingno、CSSoverflow: hidden这些操作在跨域下是可以做的因为它属于父页面对自己内部元素样式的控制不涉及访问子页面内部 DOM。容易混淆的点是你能改 iframe 标签的宽高不代表你能读 iframe 内部的内容高度。跨域下想根据子页面内容自适应高度还是得靠子页面把高度数据通过 postMessage 传给父页面父页面再动态设置 iframe 的高度。这是 postMessage 特别实用的一类场景。7.8 跨域状态下操作父页面 DOM 或调用函数跨域后子页面访问window.parent.document会得到跨域错误调用window.parent.someFunc()也不行。唯一能依赖的就是window.parent.postMessage和父页面的监听器。所以设计上要提前把“子页面需要父页面配合的动作”抽象成消息而不是考虑能不能直接调函数。8. 通信方案选型与拓展从 postMessage 到更完善的消息体系8.1 postMessage 和 localStorage、BroadcastChannel 怎么选有些场景你可能会纠结除了 postMessage还有没有别的跨窗口通信方案方案适用场景跨域能力局限postMessageiframe、window.open 等窗口间通信支持需要双方各写监听和发送逻辑localStorage storage 事件同源多标签页不支持跨域同源才能共享存储BroadcastChannel同源多标签页/同源 iframe同源跨域不行Cookie 轮询子域相同场景部分支持改 document.domain、轮询有延迟结论很明确凡是真正跨域的 iframe 通信postMessage 是唯一稳妥的内置方案。postMessage 配合 message 事件能做到真正的双向、即时、结构化数据传输。同源场景下你可以按需选 localStorage 或 BroadcastChannel但它们替代不了 postMessage 的跨域能力。8.2 会话凭证跨域传递的思路还有一个高频需求父页面登录了子页面怎么拿到登录态如果子页面和父页面不同域Cookie 默认不会自动带过去。常见做法是父页面通过 postMessage 把 token 或短暂的授权码发给子页面子页面拿到后再自己调用后端接口换 session 或临时凭证。要在消息里加一个过期时间或者一次性标识降低 token 泄露被重放的风险。这种场景下postMessage 的安全要求更高origin 校验、数据加密比如用现有密钥对 token 做简单包装都值得做。8.3 要不要自己封装一套消息总线当项目里父子通信的频次变高就会出现几十种 message type代码里到处是window.addEventListener(message)维护成本开始上升。这时候可以考虑封装一个简单的事件总线class FrameMessenger { constructor(origin, getTargetWindow) { this.origin origin; this.getTargetWindow getTargetWindow; this.listeners new Map(); this.handler this.handleMessage.bind(this); window.addEventListener(message, this.handler); } send(type, payload) { const target this.getTargetWindow(); if (!target) return; target.postMessage({ type, payload }, this.origin); } on(type, callback) { if (!this.listeners.has(type)) { this.listeners.set(type, []); } this.listeners.get(type).push(callback); } handleMessage(event) { if (event.origin ! this.origin) return; const msg event.data; if (!msg || !msg.type) return; const cbs this.listeners.get(msg.type); if (cbs) { cbs.forEach(cb cb(msg.payload, event)); } } destroy() { window.removeEventListener(message, this.handler); this.listeners.clear(); } }使用起来就清爽了两边各自 new messenger业务代码里只关心send和on不用再手写事件对象判断。我建议消息类型多到 10 种以上时就该做这层封装。8.4 当 iframe 加载的是后端渲染页面或第三方报表有些 iframe 内容不是传统前端页面而是服务端渲染的页面、BI 报表、数据看板。只要页面里有 JS 环境它就能用 postMessage。但如果对方是纯静态页面或者不允许你改源码那你就没法在对方页面里写监听器了这种情况只有一种办法让对方的开发配合你加一段消息监听代码否则任何前端技巧都无能为力。这也是为什么做集成平台时和第三方页面的通信协议一定要提前约定。最后再从实战角度说几句回到最初需求如果你要在系统里嵌入一个跨域的数据看板并且要和它做联动我的建议是先别急着写代码。把通信协议定好有哪些消息类型、由谁先发起、消息的 payload 结构长什么样、错误怎么反馈。协议定了postMessage 的编码只是体力活。我每次项目里这套协议稳定后基本没再因为通信问题返工过。另外一个小技巧你调试 postMessage 时可以在两边页面的控制台里分别输入window.addEventListener(message, e console.log(收到, e.origin, e.data));不用刷新页面就能看到所有消息排查问题时比单步调试好用得多。这个内容后续还能扩的方向有不少比如结合 MessageChannel 做更精细的双向通道、用 SharedWorker 做多页面消息中转、或者把 FrameMessenger 封装成跨项目共用的 npm 包。只要你想做跨窗口协作postMessage 这套是绕不开的地基。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →