芮宁 發表於 2026-3-2 16:47:00

打破同源枷锁:深入理解 postMessage 跨域通信机制

<div data-page-id="IHfNfNkwVd2O4bcErkucfUspnTR" data-lark-html-role="root" data-docx-has-block-data="true">
<h1 class="ace-line ace-line old-record-id-IHfNfNkwVd2O4bcErkucfUspnTR"><span style="font-size: 14px">作为前端开发,你一定遇到过这样的场景:主站嵌入了第三方支付的 iframe,需要同步用户登录状态;或者通过 </span><code style="font-size: 14px">window.open</code><span style="font-size: 14px"> 打开的子窗口,要向父页面传递操作结果。此时,浏览器的“同源策略”就像一道无形的墙,直接阻断了页面间的直接交互。而 </span><code style="font-size: 14px">postMessage</code><span style="font-size: 14px"> 正是为打破这道枷锁而生的 HTML5 核心 API,它让不同源的窗口、框架之间得以安全地双向通信,成为跨域交互的“官方邮差”。</span></h1>
<div class="ace-line ace-line old-record-id-G0b0flj6ndOMnwcie7rykvEgpxa">本文将从同源策略的核心限制说起,深入剖析 <code>postMessage</code> 的工作原理、语法细节,通过 3 个实战场景的完整代码示例,结合企业级安全规范与避坑指南,帮你彻底掌握这项技术,从容应对各类跨域通信需求。</div>
<h2 class="heading-2 ace-line old-record-id-MwQEfC7NddihRbcE8QB2X9qbs3w">一、同源策略:跨域通信的“天然壁垒”</h2>
<div class="ace-line ace-line old-record-id-JBovfggmEdzD3Xcp8PqfXnIgjIP">要理解 <code>postMessage</code> 的价值,首先要搞清楚它解决的核心问题——同源策略(Same-Origin Policy)。这是浏览器为保护用户信息安全而设立的核心安全准则,它规定:<strong>只有当两个页面的协议、域名、端口完全一致时,才能互相访问对方的 DOM、变量、函数或发送请求</strong>。</div>
<h3 class="heading-3 ace-line old-record-id-YkeKfsNOKdC20fcdyExQnOvhc6G">1. 同源与跨域的直观判断</h3>
<div class="ace-line ace-line old-record-id-INt6fzL83duZrUcS6jJosZQdhLB">以下是同源与跨域的典型示例(以 <code>https://www.example.com:443</code> 为基准):</div>
<div>
<table class="ace-table" data-ace-table-col-widths="200;200;200"><colgroup><col width="200"><col width="200"><col width="200"></colgroup>
<thead>
<tr><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-KIjsfWGY4dDn87cFRhgsG047MAG">页面地址</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-B10of7XzRdnhBgc8qwVbKkDcOxJ">是否同源</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-JCUsfacoBdYiy3ccu8g4zsjczhO">原因</div>
</th></tr>
</thead>
<tbody>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-RsprftV5RdyWOIc4yZ4LtiNhVIs"><code>https://www.example.com:443/home</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-BSKyfjWQ5dGHiwcADg6ZE4Uegel">是</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-JFaHfvX1xdgYT5cffTD8ckEb2Pp">协议、域名、端口完全一致</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-QtrlfaI5vdkQkrcSbLCHQaWeFaN"><code>https://blog.example.com:443</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-EwBCfQVS0doaiZcbZNThEb8YykT">否</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-VUwGfktcUdAxWWcQUM1r3xnhE12">子域名不同</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-HpscfCqmOdIHmqcIWJ6Sb7XeCHe"><code>http://www.example.com:443</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-HLe7fg8kQdASAfcykZ6rvxIeazu">否</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-GBEsfj79BdHYyBcSdkgMgBjg8j5">协议不同(http vs https)</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-ViE9f22aZdt33mcqKGKUfznct8G"><code>https://www.example.com:8080</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-GO14fWU6Yd4boPc0fbglK9bebOh">否</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-WW3nfVCN9dr7e4cSdIX5lCZbHJe">端口不同(443 vs 8080)</div>
</td>
</tr>
</tbody>
</table>
</div>
<h3 class="heading-3 ace-line old-record-id-IejqfJrRTd0s5BcRfKukEMwhsR2">2. 同源策略的核心限制</h3>
<div class="ace-line ace-line old-record-id-SY2Yf4o2zdE4bAcvbi5gnCufx3Z">在跨域场景下,浏览器会严格限制以下操作:</div>
<ol class="list-number1" start="1">
<li class="ace-line ace-line old-record-id-ZtE0fIin1dox3BcAgz2A4YEg8Ay" data-list="number">禁止跨域访问 DOM:父页面无法获取跨域 iframe 的 <code>contentDocument</code>,子页面也无法读取父页面的 <code>window</code> 属性;</li>
<li class="ace-line ace-line old-record-id-U7dkfFrdVdfYfmc7IMCsn2ZgbAH" data-list="number">禁止跨域脚本调用:无法直接调用跨域页面的函数或修改变量;</li>
<li class="ace-line ace-line old-record-id-BArZfZCsBdBHoRcSZQgtLvVVuyk" data-list="number">禁止跨域数据共享:LocalStorage、SessionStorage 等存储对象无法跨域访问。</li>
</ol>
<div class="ace-line ace-line old-record-id-H0dGf68XmdAzPbc4gVigKG3d2Oj">这些限制虽然保障了安全,但也给实际开发带来了诸多不便。在 <code>postMessage</code> 出现之前,开发者只能通过 JSONP、CORS 代理、服务器中转等方式间接实现跨域通信,不仅开发成本高,还存在功能局限性(如 JSONP 仅支持 GET 请求)。而 <code>postMessage</code> 的出现,让前端跨域通信有了标准化、高效的解决方案。</div>
<h2 class="heading-2 ace-line old-record-id-CQ2mfSG9OdaUsnc28e7YGUhhx3J">二、postMessage 核心原理与语法详解</h2>
<div class="ace-line ace-line old-record-id-USoVf7XV7d588dco8GnMpKbg5Nj"><code>postMessage</code> 是挂载在 <code>window</code> 对象上的方法,它的核心设计思想是<strong>基于消息事件的异步通信机制</strong>:发送方通过调用 <code>postMessage</code> 方法,向目标窗口发送结构化数据;接收方通过监听 <code>message</code> 事件,捕获并处理来自合法源的消息。</div>
<div class="ace-line ace-line old-record-id-HSLBfKlnpdDaBmcX7Slx2Aigqwg">与传统跨域方案不同,<code>postMessage</code> 不依赖服务器中转,而是由浏览器直接提供通信通道,同时通过“源校验”机制保障通信安全,真正实现了“受控的跨域突破”。</div>
<h3 class="heading-3 ace-line old-record-id-INO4fFhRMda0Iccm0r3tnPWfwDh">1. 核心语法与参数说明</h3>
<div class="ace-line ace-line old-record-id-OdUdfyFjMddW4ZcOedIYLwUbcos"><code>postMessage</code> 的语法非常简洁,核心方法与事件监听的完整格式如下:</div>
<h4 class="heading-4 ace-line old-record-id-KqMHf2pIOdfxV2cfGLtBHjvhN3i">发送方:targetWindow.postMessage()</h4>
<div class="cnblogs_code">
<pre>targetWindow.postMessage(message, targetOrigin, );</pre>
</div>
<p>&nbsp;</p>
<div class="ace-line ace-line old-record-id-SeZJfOAZgdmKUtc0m3hJdajgECp">该方法接收三个参数,其中前两个为必选,第三个为可选,具体说明如下:</div>
<div>
<table class="ace-table" data-ace-table-col-widths="200;200;200;200"><colgroup><col width="200"><col width="200"><col width="200"><col width="200"></colgroup>
<thead>
<tr><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-ZN3DfbtfHdH4A6c6PRzzMPcgIAW">参数</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-QFO1f2BaEdwa54c7a3PdJzthqxD">类型</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-FKKOfSsUcdxAhycw19O8ahddXAe">核心说明</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-KKTAfojkgdeIcNcMsW0Wzvyfy0G">安全要点</div>
</th></tr>
</thead>
<tbody>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-QhohfnAHedmUWRc9EP2Y8D4Szwp"><code>message</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-WMgbfZGttdKZ53c4ec1DD4OdNsH">任意类型</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-KSc7fGWoHdnbuOcgPrdGmLTbW9F">要发送的消息数据,支持字符串、对象、数组等。浏览器会通过“结构化克隆算法”自动序列化,无需手动转 JSON</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-FCgAf1KSxdsCyWcacDuEh8QLHrO">避免发送敏感数据(如密码),即使加密也需谨慎</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-Vbl5fMeJrd7iOYcdBHWU28DeIWN"><code>targetOrigin</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-NRNLfOrwbdTB73c0Q8avvmJbzWV">字符串</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-KL8gfxdXrdEn6lcChiPwO1lefTg">目标窗口的“源”(协议+域名+端口),如 <code>https://pay.example.com</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-Xx3TfrypHdutsjckGeUKSThcXdM"><strong>生产环境禁止使用 </strong><strong><code>*</code></strong>(通配符),否则会将消息发送给任意源,存在数据泄露风险</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-VTMTfDj4pdoIyzcKW1VI1xYQdNs"><code>transfer</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-XwMFfOQ1gdPocwc4sLYHwShhoe5">数组</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-AAVff31uKdxSYFcrHreehiWcvbq">可选的可转移对象(如 ArrayBuffer),转移后发送方无法再使用该对象</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-GOz4f4TxmdyAp6cI8feiCfog47d">日常开发极少使用,仅适用于大数据传输场景</div>
</td>
</tr>
</tbody>
</table>
</div>
<h4 class="heading-4 ace-line old-record-id-Dgt5fc8m2dQztecoFJTHyKeEc2F">接收方:监听 message 事件</h4>
<div class="ace-line ace-line old-record-id-VAzMftBj0dnqJdc8BXkBv3hhaiq">接收方需要在窗口上监听 <code>message</code> 事件,当有消息到达时,会触发回调函数,回调参数为 <code>MessageEvent</code> 对象,包含三个核心属性:</div>
<div>
<table class="ace-table" data-ace-table-col-widths="200;200;200;200"><colgroup><col width="200"><col width="200"><col width="200"><col width="200"></colgroup>
<thead>
<tr><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-CuMFfcfQcdMtRFc1qnDLZAdwhcp">属性</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-M9z7fVndxdZBEKcav4bO8vMgs1O">类型</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-HUJIf5rkYd7xttcJ73BDytweDJu">核心说明</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-TVQZfV5Mide8e8cAEyz62hBLPJe">校验要点</div>
</th></tr>
</thead>
<tbody>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-RAHEfXvL6defZocdni7Rmfdenec"><code>event.data</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-He27fnme1dosBzcxVtV1o6vbcyw">任意类型</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-S3dEfqII0dAKTlcJ9WqYQdkfJJr">发送方传递的消息数据,与发送时的 <code>message</code> 一致</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-QAVtfNTnAdS0LZcDSVC821tdiyk">需校验数据类型和格式,防止恶意数据注入</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-EPJAfImEidMMBIc3zePqvnZdtlf"><code>event.origin</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-PIP6fOdEfdvrYUcUUOjsScIfHuy">字符串</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-BbnjfdrvkdjNWOcxSYRDBhvgYwt">发送方的源(浏览器强制注入,不可篡改),如 <code>https://www.example.com</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-YtdXfsv6Jdran7cari0Lf4tURmH"><strong>唯一可信的身份凭证</strong>,必须严格校验</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-Pe7wfwb0udxBvDc9CxPKCjLc4B6"><code>event.source</code></div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-GLL2fr3AEd4XCWcfpqRygH2gidu">Window 对象</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-TRUEfGKXldUMMQcQfWqFBTsc9hd">发送方窗口的引用,可用于向发送方回传消息</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-WXL4fwwBedw3b7c5Kx26Mb4eqFB">可通过该对象实现双向通信,无需重新获取窗口引用</div>
</td>
</tr>
</tbody>
</table>
</div>
<h3 class="heading-3 ace-line old-record-id-T4wsfe862desHbc4lspSH4ad7HA">2. 关键概念:窗口引用的获取方式</h3>
<div class="ace-line ace-line old-record-id-VdFKfuJX2dzXNyczW0pyR9Hhvey">要调用 <code>postMessage</code>,首先需要获取<strong>目标窗口的引用</strong>(<code>targetWindow</code>),不同通信场景的获取方式不同,这是实现通信的前提,常见方式如下:</div>
<ol class="list-number1" start="1">
<li class="ace-line ace-line old-record-id-SMslfCmYhd6xihcEDFJ8ynedxlx" data-list="number"><strong>iframe 场景</strong>:父页面通过 <code>iframe.contentWindow</code> 获取子窗口引用;子页面通过 <code>window.parent</code>(父窗口)或 <code>window.top</code>(顶级窗口)获取父级引用;</li>
<li class="ace-line ace-line old-record-id-XnbwfoxPtdGPTlc5B8tFl8LgVsj" data-list="number"><strong>新窗口场景</strong>:父页面通过 <code>window.open(url)</code> 的返回值获取子窗口引用;子页面通过 <code>window.opener</code> 获取父窗口引用;</li>
<li class="ace-line ace-line old-record-id-G4IgfpCSBdl4Dec0LiTC5NWbrBf" data-list="number"><strong>多标签页场景</strong>:通过 <code>localStorage</code> 结合 <code>storage</code> 事件触发,再通过 <code>window.open</code> 或已知的窗口引用通信(需配合其他机制)。</li>
</ol>
<h2 class="heading-2 ace-line old-record-id-UxfsfbnbWdpI9vc5OUDxgM1gx5O">三、实战场景:完整代码示例</h2>
<div class="ace-line ace-line old-record-id-XEt4ferA7ddXJrc74bMEwlhfKoN">为了让你真正掌握 <code>postMessage</code> 的使用,我们选取 3 个开发中最常见的跨域场景,搭建本地测试环境,提供完整的可运行代码,并标注关键安全要点。</div>
<h3 class="heading-3 ace-line old-record-id-R2Yafe0oOdeigZcFpCaV9qNcDxk">前置准备:搭建本地跨域测试环境</h3>
<div class="ace-line ace-line old-record-id-QYH9fkx6CdKMDHclgu59plSeZ94">由于浏览器的同源策略限制,我们需要在本地模拟两个不同的域名。通过修改 <code>hosts</code> 文件(Windows 路径:<code>C:\Windows\System32\drivers\etc\hosts</code>;Mac/Linux 路径:<code>/etc/hosts</code>),添加以下映射:</div>
<div class="cnblogs_code">
<pre>127.0.0.1<span style="color: rgba(0, 0, 0, 1)">parent.example.com
</span>127.0.0.1child.example.com</pre>
</div>
<p>&nbsp;</p>
<div class="ace-line ace-line old-record-id-ATpCfRzWedhAQWcgku7cG7GqptF">然后启动两个本地服务:</div>
<ul class="list-bullet1">
<li class="ace-line ace-line old-record-id-TLIIfF1mEdnL7ZcpXmeLAJUhbnF" data-list="bullet">父页面服务:运行在 <code>http://parent.example.com:8080</code>;</li>
<li class="ace-line ace-line old-record-id-U9CifIp7HdaBdZc0IBgNLX0gzFj" data-list="bullet">子页面服务:运行在 <code>http://child.example.com:8081</code>。</li>
</ul>
<h3 class="heading-3 ace-line old-record-id-XIu2fojWodrko4c5RUlqdpkNmHT">场景一:iframe 父子页面双向跨域通信</h3>
<div class="ace-line ace-line old-record-id-H6MVfo1hNdrcyxcwpSH2iTYn3PX">这是最常见的场景,例如主站(parent.example.com)嵌入第三方组件(child.example.com),需要实现“父传子(同步用户信息)”和“子传父(同步操作结果)”。</div>
<h4 class="heading-4 ace-line old-record-id-LqFYfy7WQd91wScE0X8GUhlgnuM">1. 父页面(发送方 + 接收方):http://parent.example.com:8080/index.html</h4>
<div class="cnblogs_code">
<pre>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;父页面 - 跨域通信测试&lt;/title&gt;
    &lt;style&gt;<span style="color: rgba(0, 0, 0, 1)">
      .container { margin: 20px; }
      iframe { width: </span>100%<span style="color: rgba(0, 0, 0, 1)">; height: 300px; border: 1px solid #ccc; }
      .log</span>-box { margin-top: 20px; padding: 10px; border: 1px solid #0066cc; height: 150px; overflow-<span style="color: rgba(0, 0, 0, 1)">y: auto; }
    </span>&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="container"&gt;
      &lt;h1&gt;父页面(parent.example.com:8080)&lt;/h1&gt;
      &lt;button onclick="sendToChild()"&gt;向子页面发送用户信息&lt;/button&gt;
      &lt;iframe id="childIframe" src="http://child.example.com:8081/child.html"&gt;&lt;/iframe&gt;
      &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="log-box" id="receiveLog"&gt;接收日志:&lt;br&gt;&lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 存储子窗口引用(确保 iframe 加载完成后获取)</span>
      let childWindow = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">const</span> childIframe = document.getElementById('childIframe'<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">const</span> receiveLog = document.getElementById('receiveLog'<span style="color: rgba(0, 0, 0, 1)">);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 监听 iframe 加载完成事件,获取子窗口引用</span>
      childIframe.onload =<span style="color: rgba(0, 0, 0, 1)"> function() {
            childWindow </span>=<span style="color: rgba(0, 0, 0, 1)"> childIframe.contentWindow;
            console.log(</span>'子页面加载完成,已获取窗口引用'<span style="color: rgba(0, 0, 0, 1)">);
      };

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 向子页面发送消息(父 -&gt; 子)</span>
<span style="color: rgba(0, 0, 0, 1)">      function sendToChild() {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">childWindow) {
                alert(</span>'子页面尚未加载完成!'<span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 构造用户信息(模拟业务数据)</span>
            <span style="color: rgba(0, 0, 255, 1)">const</span> userData =<span style="color: rgba(0, 0, 0, 1)"> {
                cmd: </span>'userLogin'<span style="color: rgba(0, 0, 0, 1)">,
                data: {
                  userId: </span>10086<span style="color: rgba(0, 0, 0, 1)">,
                  userName: </span>'前端开发狮'<span style="color: rgba(0, 0, 0, 1)">,
                  token: </span>'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 模拟加密 token</span>
<span style="color: rgba(0, 0, 0, 1)">                },
                timestamp: Date.now()
            };
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关键:指定明确的 targetOrigin,禁止使用 *</span>
            childWindow.postMessage(userData, 'http://child.example.com:8081'<span style="color: rgba(0, 0, 0, 1)">);
            console.log(</span>'已向子页面发送用户信息:'<span style="color: rgba(0, 0, 0, 1)">, userData);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3. 监听子页面的消息(子 -&gt; 父)</span>
      window.addEventListener('message', handleMessage, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 核心:消息处理与安全校验函数</span>
<span style="color: rgba(0, 0, 0, 1)">      function handleMessage(event) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第一步:严格校验发送方源(仅接收 child.example.com:8081 的消息)</span>
            <span style="color: rgba(0, 0, 255, 1)">const</span> trustedOrigin = 'http://child.example.com:8081'<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.origin !==<span style="color: rgba(0, 0, 0, 1)"> trustedOrigin) {
                console.warn(</span>'拒绝接收未知源的消息:'<span style="color: rgba(0, 0, 0, 1)">, event.origin);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第二步:校验消息格式(确保是预期的业务数据)</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (typeof event.data !== 'object' || event.data === <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                console.warn(</span>'消息格式非法,非对象类型'<span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第三步:根据业务指令处理消息</span>
            <span style="color: rgba(0, 0, 255, 1)">const</span> { cmd, data, timestamp } =<span style="color: rgba(0, 0, 0, 1)"> event.data;
            </span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (cmd) {
                </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'paySuccess'<span style="color: rgba(0, 0, 0, 1)">:
                  logReceive(`子页面通知:支付成功,订单号:${data.orderNo}`);
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 业务逻辑:更新页面状态,隐藏支付框</span>
                  <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'payCancel'<span style="color: rgba(0, 0, 0, 1)">:
                  logReceive(`子页面通知:用户取消支付`);
                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
                  logReceive(`收到未知指令:${cmd},数据:${JSON.stringify(data)}`);
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可选:向子页面回传确认消息(实现双向通信)</span>
<span style="color: rgba(0, 0, 0, 1)">            event.source.postMessage({
                cmd: </span>'ack'<span style="color: rgba(0, 0, 0, 1)">,
                msg: </span>'父页面已收到消息'<span style="color: rgba(0, 0, 0, 1)">
            }, event.origin);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 辅助函数:打印接收日志</span>
<span style="color: rgba(0, 0, 0, 1)">      function logReceive(content) {
            receiveLog.innerHTML </span>+= `[${<span style="color: rgba(0, 0, 255, 1)">new</span> Date().toLocaleTimeString()}] ${content}&lt;br&gt;<span style="color: rgba(0, 0, 0, 1)">`;
            receiveLog.scrollTop </span>=<span style="color: rgba(0, 0, 0, 1)"> receiveLog.scrollHeight;
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 避坑:页面卸载时移除事件监听,防止内存泄漏</span>
      window.addEventListener('beforeunload'<span style="color: rgba(0, 0, 0, 1)">, function() {
            window.removeEventListener(</span>'message'<span style="color: rgba(0, 0, 0, 1)">, handleMessage);
      });
    </span>&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p>&nbsp;</p>
<h4 class="heading-4 ace-line old-record-id-OXzrfh94id2i4RcJtOmfrTzfC3l">2. 子页面(接收方 + 发送方):http://child.example.com:8081/child.html</h4>
<div class="cnblogs_code">
<pre>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;子页面 - 跨域通信测试&lt;/title&gt;
    &lt;style&gt;<span style="color: rgba(0, 0, 0, 1)">
      .container { margin: 20px; }
      .log</span>-box { margin: 10px 0; padding: 10px; border: 1px solid #009933; height: 150px; overflow-<span style="color: rgba(0, 0, 0, 1)">y: auto; }
    </span>&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="container"&gt;
      &lt;h1&gt;子页面(child.example.com:8081)&lt;/h1&gt;
      &lt;button onclick="sendPaySuccess()"&gt;通知父页面:支付成功&lt;/button&gt;
      &lt;button onclick="sendPayCancel()"&gt;通知父页面:取消支付&lt;/button&gt;
      &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="log-box" id="receiveLog"&gt;接收日志:&lt;br&gt;&lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;
      <span style="color: rgba(0, 0, 255, 1)">const</span> receiveLog = document.getElementById('receiveLog'<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可信源:仅接收 parent.example.com:8080 的消息</span>
      <span style="color: rgba(0, 0, 255, 1)">const</span> trustedOrigin = 'http://parent.example.com:8080'<span style="color: rgba(0, 0, 0, 1)">;

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 监听父页面的消息(父 -&gt; 子)</span>
      window.addEventListener('message', handleParentMessage, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 核心:处理父页面消息,严格校验</span>
<span style="color: rgba(0, 0, 0, 1)">      function handleParentMessage(event) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第一步:校验源</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (event.origin !==<span style="color: rgba(0, 0, 0, 1)"> trustedOrigin) {
                console.warn(</span>'拒绝未知源消息:'<span style="color: rgba(0, 0, 0, 1)">, event.origin);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第二步:校验消息格式和指令</span>
            <span style="color: rgba(0, 0, 255, 1)">const</span> { cmd, data, timestamp } = event.data ||<span style="color: rgba(0, 0, 0, 1)"> {};
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!cmd || typeof data !== 'object'<span style="color: rgba(0, 0, 0, 1)">) {
                console.warn(</span>'无效的业务消息:'<span style="color: rgba(0, 0, 0, 1)">, event.data);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第三步:处理业务逻辑</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (cmd === 'userLogin'<span style="color: rgba(0, 0, 0, 1)">) {
                logReceive(`收到用户登录信息:用户名</span>=${data.userName},Token=${data.token.substring(0, 20<span style="color: rgba(0, 0, 0, 1)">)}...`);
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 业务逻辑:存储用户信息,初始化支付组件</span>
                console.log('初始化支付组件,用户ID:'<span style="color: rgba(0, 0, 0, 1)">, data.userId);
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收父页面的确认消息</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (cmd === 'ack'<span style="color: rgba(0, 0, 0, 1)">) {
                logReceive(`父页面确认:${data.msg}`);
            }
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 向父页面发送支付结果(子 -&gt; 父)</span>
<span style="color: rgba(0, 0, 0, 1)">      function sendPaySuccess() {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 子页面向父页面发送消息,使用 window.parent 获取父窗口引用
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关键:targetOrigin 设为父页面的源,或使用 event.origin(若有)</span>
<span style="color: rgba(0, 0, 0, 1)">            window.parent.postMessage({
                cmd: </span>'paySuccess'<span style="color: rgba(0, 0, 0, 1)">,
                data: {
                  orderNo: `ORDER_${Date.now()}`,
                  amount: </span>99.00<span style="color: rgba(0, 0, 0, 1)">
                },
                timestamp: Date.now()
            }, trustedOrigin);
      }

      function sendPayCancel() {
            window.parent.postMessage({
                cmd: </span>'payCancel'<span style="color: rgba(0, 0, 0, 1)">,
                data: {},
                timestamp: Date.now()
            }, trustedOrigin);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 辅助函数:打印日志</span>
<span style="color: rgba(0, 0, 0, 1)">      function logReceive(content) {
            receiveLog.innerHTML </span>+= `[${<span style="color: rgba(0, 0, 255, 1)">new</span> Date().toLocaleTimeString()}] ${content}&lt;br&gt;<span style="color: rgba(0, 0, 0, 1)">`;
            receiveLog.scrollTop </span>=<span style="color: rgba(0, 0, 0, 1)"> receiveLog.scrollHeight;
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 页面卸载时移除监听</span>
      window.addEventListener('beforeunload'<span style="color: rgba(0, 0, 0, 1)">, function() {
            window.removeEventListener(</span>'message'<span style="color: rgba(0, 0, 0, 1)">, handleParentMessage);
      });
    </span>&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p>&nbsp;</p>
<h4 class="heading-4 ace-line old-record-id-VYvwfRPhcdU29TcfgdGzmAzfZB7">场景一关键要点</h4>
<ol class="list-number1" start="1">
<li class="ace-line ace-line old-record-id-WvfOfXVTddbvC6cuLJDtW7UkNHJ" data-list="number"><strong>iframe 加载时机</strong>:必须在 <code>iframe.onload</code> 事件后获取 <code>contentWindow</code>,否则会因子页面未加载完成导致引用为空;</li>
<li class="ace-line ace-line old-record-id-VuaDfemL3di4OAcstgUATkjfoFT" data-list="number"><strong>双向校验</strong>:父、子页面均严格校验 <code>event.origin</code>,确保消息来自可信源;</li>
<li class="ace-line ace-line old-record-id-LXPbfCoGedEYRrcekK8CUSMhIwC" data-list="number"><strong>业务指令设计</strong>:通过 <code>cmd</code> 字段区分业务类型(如 <code>userLogin</code>、<code>paySuccess</code>),让消息处理更清晰;</li>
<li class="ace-line ace-line old-record-id-LE4Cfb7niddcsxc8NOi8JqZeIKh" data-list="number"><strong>内存泄漏防护</strong>:页面卸载时移除 <code>message</code> 事件监听,避免长期占用内存。</li>
</ol>
<h3 class="heading-3 ace-line old-record-id-BPfzfWQhCdQFqCcr1CBzgxogp0Q">场景二:window.open 新窗口与父页面通信</h3>
<div class="ace-line ace-line old-record-id-SmK2fSPmOdQJ0ecB9kPDKKrfvNH">该场景适用于“点击按钮打开新窗口,完成操作后返回结果”的需求,例如弹出的登录窗口、订单详情窗口。</div>
<h4 class="heading-4 ace-line old-record-id-XnCyfjAZSdZhRWciu1v1Mowv1Zg">1. 父页面(打开新窗口 + 接收消息):http://parent.example.com:8080/open-parent.html</h4>
<div class="cnblogs_code">
<pre>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;父页面 - 新窗口通信&lt;/title&gt;
    &lt;style&gt;<span style="color: rgba(0, 0, 0, 1)">
      .container { margin: 20px; }
      .log</span>-box { margin-top: 20px; padding: 10px; border: 1px solid #0066cc; height: 150px; overflow-<span style="color: rgba(0, 0, 0, 1)">y: auto; }
    </span>&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="container"&gt;
      &lt;h1&gt;父页面(parent.example.com:8080)&lt;/h1&gt;
      &lt;button onclick="openChildWindow()"&gt;打开子窗口&lt;/button&gt;
      &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="log-box" id="receiveLog"&gt;接收日志:&lt;br&gt;&lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;<span style="color: rgba(0, 0, 0, 1)">
      let childWin </span>= <span style="color: rgba(0, 0, 255, 1)">null</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 存储新窗口引用</span>
      <span style="color: rgba(0, 0, 255, 1)">const</span> receiveLog = document.getElementById('receiveLog'<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">const</span> trustedOrigin = 'http://child.example.com:8081'<span style="color: rgba(0, 0, 0, 1)">;

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 打开新窗口</span>
<span style="color: rgba(0, 0, 0, 1)">      function openChildWindow() {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打开子窗口,指定尺寸和位置</span>
            childWin =<span style="color: rgba(0, 0, 0, 1)"> window.open(
                </span>'http://child.example.com:8081/open-child.html'<span style="color: rgba(0, 0, 0, 1)">,
                </span>'_blank'<span style="color: rgba(0, 0, 0, 1)">,
                </span>'width=600,height=400,left=200,top=100'<span style="color: rgba(0, 0, 0, 1)">
            );

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 监听子窗口关闭事件(可选)</span>
            <span style="color: rgba(0, 0, 255, 1)">const</span> checkClose = setInterval(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (childWin.closed) {
                  clearInterval(checkClose);
                  logReceive(</span>'子窗口已关闭'<span style="color: rgba(0, 0, 0, 1)">);
                  childWin </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
                }
            }, </span>500<span style="color: rgba(0, 0, 0, 1)">);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 向子窗口发送消息(需等待子窗口加载完成)</span>
<span style="color: rgba(0, 0, 0, 1)">      function sendToChild() {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!childWin ||<span style="color: rgba(0, 0, 0, 1)"> childWin.closed) {
                alert(</span>'子窗口未打开或已关闭!'<span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            childWin.postMessage({
                cmd: </span>'init'<span style="color: rgba(0, 0, 0, 1)">,
                data: {
                  title: </span>'订单支付'<span style="color: rgba(0, 0, 0, 1)">,
                  orderId: </span>'OD202603021600'<span style="color: rgba(0, 0, 0, 1)">
                }
            }, trustedOrigin);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3. 监听子窗口的消息</span>
      window.addEventListener('message', handleChildMessage, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);

      function handleChildMessage(event) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.origin !== trustedOrigin) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (typeof event.data !== 'object') <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;

            </span><span style="color: rgba(0, 0, 255, 1)">const</span> { cmd, data } =<span style="color: rgba(0, 0, 0, 1)"> event.data;
            </span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (cmd) {
                </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'operateResult'<span style="color: rgba(0, 0, 0, 1)">:
                  logReceive(`子窗口返回:${data.result},备注:${data.remark}`);
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可选:向子窗口发送确认消息</span>
                  event.source.postMessage({ cmd: 'ack', msg: '结果已收到'<span style="color: rgba(0, 0, 0, 1)"> }, event.origin);
                  </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
                </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">:
                  logReceive(`未知指令:${cmd}`);
            }
      }

      function logReceive(content) {
            receiveLog.innerHTML </span>+= `[${<span style="color: rgba(0, 0, 255, 1)">new</span> Date().toLocaleTimeString()}] ${content}&lt;br&gt;<span style="color: rgba(0, 0, 0, 1)">`;
            receiveLog.scrollTop </span>=<span style="color: rgba(0, 0, 0, 1)"> receiveLog.scrollHeight;
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 页面卸载时清理资源</span>
      window.addEventListener('beforeunload'<span style="color: rgba(0, 0, 0, 1)">, function() {
            window.removeEventListener(</span>'message'<span style="color: rgba(0, 0, 0, 1)">, handleChildMessage);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (childWin &amp;&amp; !<span style="color: rgba(0, 0, 0, 1)">childWin.closed) {
                childWin.close();
            }
      });
    </span>&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p>&nbsp;</p>
<h4 class="heading-4 ace-line old-record-id-OaT8fjNiVd3VbYcLLTM1cHyfcDP">2. 子页面(接收消息 + 向父页面发送结果):http://child.example.com:8081/open-child.html</h4>
<div class="cnblogs_code">
<pre>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;子窗口 - 通信测试&lt;/title&gt;
    &lt;style&gt;<span style="color: rgba(0, 0, 0, 1)">
      .container { margin: 20px; }
    </span>&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="container"&gt;
      &lt;h1&gt;子窗口(child.example.com:8081)&lt;/h1&gt;
      &lt;p id="initInfo"&gt;等待父页面初始化...&lt;/p&gt;
      &lt;button onclick="sendResult('success')"&gt;返回:操作成功&lt;/button&gt;
      &lt;button onclick="sendResult('fail')"&gt;返回:操作失败&lt;/button&gt;
      &lt;button onclick="closeWindow()"&gt;关闭窗口&lt;/button&gt;
    &lt;/div&gt;

    &lt;script&gt;
      <span style="color: rgba(0, 0, 255, 1)">const</span> initInfo = document.getElementById('initInfo'<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">const</span> trustedOrigin = 'http://parent.example.com:8080'<span style="color: rgba(0, 0, 0, 1)">;

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 监听父页面的初始化消息</span>
      window.addEventListener('message', handleParentInit, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);

      function handleParentInit(event) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.origin !== trustedOrigin) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">const</span> { cmd, data } =<span style="color: rgba(0, 0, 0, 1)"> event.data;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cmd === 'init'<span style="color: rgba(0, 0, 0, 1)">) {
                initInfo.innerText </span>=<span style="color: rgba(0, 0, 0, 1)"> `初始化完成:${data.title},订单ID:${data.orderId}`;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cmd === 'ack'<span style="color: rgba(0, 0, 0, 1)">) {
                alert(`父页面确认:${event.data.msg}`);
            }
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 向父页面发送操作结果</span>
<span style="color: rgba(0, 0, 0, 1)">      function sendResult(result) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 子窗口通过 window.opener 获取父窗口引用</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (!window.opener ||<span style="color: rgba(0, 0, 0, 1)"> window.opener.closed) {
                alert(</span>'父窗口已关闭!'<span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            </span><span style="color: rgba(0, 0, 255, 1)">const</span> remark = result === 'success' ? '支付完成' : '用户放弃支付'<span style="color: rgba(0, 0, 0, 1)">;
            window.opener.postMessage({
                cmd: </span>'operateResult'<span style="color: rgba(0, 0, 0, 1)">,
                data: { result, remark }
            }, trustedOrigin);
      }

      function closeWindow() {
            window.close();
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 页面卸载时移除监听</span>
      window.addEventListener('beforeunload'<span style="color: rgba(0, 0, 0, 1)">, function() {
            window.removeEventListener(</span>'message'<span style="color: rgba(0, 0, 0, 1)">, handleParentInit);
      });
    </span>&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p>&nbsp;</p>
<h4 class="heading-4 ace-line old-record-id-LZtmfDc3LdgcrvcrigmfZzdcP2C">场景二关键要点</h4>
<ol class="list-number1" start="1">
<li class="ace-line ace-line old-record-id-EiqPfwzfIdDHFYcaV6GxilkfpYk" data-list="number"><strong>窗口引用管理</strong>:通过 <code>window.open</code> 的返回值保存子窗口引用,同时监听 <code>childWin.closed</code> 状态,避免操作已关闭的窗口;<span style="text-decoration: underline"><span style="color: rgba(255, 255, 255, 1)"><span style="color: rgba(255, 255, 255, 1); text-decoration: underline">http://www.riftplatinumbuy.com/news/11111111111111</span></span></span></li>
<li class="ace-line ace-line old-record-id-HD2xfbhohdBkszc85yvi9qWb36p" data-list="number"><strong>opener 特性</strong>:子窗口通过 <code>window.opener</code> 访问父窗口,若父窗口关闭,<code>window.opener</code> 会变为 <code>null</code> 或 <code>closed</code> 为 <code>true</code>;<span style="text-decoration: underline"><span style="color: rgba(255, 255, 255, 1)"><span style="color: rgba(255, 255, 255, 1); text-decoration: underline">http://www.riftplatinumbuy.com/news/2222222222222</span></span></span></li>
<li class="ace-line ace-line old-record-id-PRiUfqxg9dt1g5cEKzYybmabxGk" data-list="number"><strong>兼容性</strong>:部分浏览器会拦截 <code>window.open</code>(如弹出窗口拦截器),开发时需提示用户允许弹出窗口。<span style="text-decoration: underline"><span style="color: rgba(255, 255, 255, 1)"><span style="color: rgba(255, 255, 255, 1); text-decoration: underline">http://www.riftplatinumbuy.com/news/33333333333333</span></span></span></li>
</ol>
<h3 class="heading-3 ace-line old-record-id-VrOWfFzFGdQHGWchyLgJvMJpXuv">场景三:复杂场景——iframe 兄弟页面跨域通信<span style="text-decoration: underline"><span style="color: rgba(255, 255, 255, 1)"><span style="color: rgba(255, 255, 255, 1); text-decoration: underline">http://www.riftplatinumbuy.com/news/5555555555</span></span></span></h3>
<div class="ace-line ace-line old-record-id-RVjMfkdw3dWdO1c6WiqprncbesK">兄弟页面(同一父页面下的两个跨域 iframe)无法直接通信,需通过<strong>父页面中转</strong>实现,这是微前端、多组件嵌入场景中的常见需求。<span style="text-decoration: underline"><span style="color: rgba(255, 255, 255, 1)"><span style="color: rgba(255, 255, 255, 1); text-decoration: underline">http://www.riftplatinumbuy.com/news/4444444444</span></span></span></div>
<h4 class="heading-4 ace-line old-record-id-Oh0qf1aMXdywOncCtTyo4KXe6LG">1. 父页面(中转中心):http://parent.example.com:8080/brother-parent.html</h4>
<div class="cnblogs_code">
<pre>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;父页面 - 兄弟 iframe 中转&lt;/title&gt;
    &lt;style&gt;<span style="color: rgba(0, 0, 0, 1)">
      .container { margin: 20px; }
      .iframe</span>-group { display: flex; gap: 20px; margin: 20px 0<span style="color: rgba(0, 0, 0, 1)">; }
      iframe { flex: </span>1<span style="color: rgba(0, 0, 0, 1)">; height: 300px; border: 1px solid #ccc; }
      .log</span>-box { padding: 10px; border: 1px solid #0066cc; height: 100px; overflow-<span style="color: rgba(0, 0, 0, 1)">y: auto; }
    </span>&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="container"&gt;
      &lt;h1&gt;父页面(中转中心)&lt;/h1&gt;
      &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="iframe-group"&gt;
            &lt;iframe id="iframeA" src="http://child.example.com:8081/brother-a.html"&gt;&lt;/iframe&gt;
            &lt;iframe id="iframeB" src="http://another.example.com:8082/brother-b.html"&gt;&lt;/iframe&gt;
      &lt;/div&gt;
      &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="log-box" id="transferLog"&gt;中转日志:&lt;br&gt;&lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 存储两个子窗口的引用</span>
      let iframeA = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      let iframeB </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">const</span> transferLog = document.getElementById('transferLog'<span style="color: rgba(0, 0, 0, 1)">);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可信源列表</span>
      <span style="color: rgba(0, 0, 255, 1)">const</span> trustedOrigins =<span style="color: rgba(0, 0, 0, 1)"> [
            </span>'http://child.example.com:8081'<span style="color: rgba(0, 0, 0, 1)">,
            </span>'http://another.example.com:8082'<span style="color: rgba(0, 0, 0, 1)">
      ];

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 获取 iframe 引用</span>
      document.getElementById('iframeA').onload =<span style="color: rgba(0, 0, 0, 1)"> function() {
            iframeA </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.contentWindow;
      };
      document.getElementById(</span>'iframeB').onload =<span style="color: rgba(0, 0, 0, 1)"> function() {
            iframeB </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.contentWindow;
      };

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 核心:监听消息并中转</span>
      window.addEventListener('message', handleTransfer, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);

      function handleTransfer(event) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第一步:校验发送方是否在可信列表中</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">trustedOrigins.includes(event.origin)) {
                console.warn(</span>'拒绝非可信源的中转请求:'<span style="color: rgba(0, 0, 0, 1)">, event.origin);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第二步:校验消息格式,必须包含目标标识</span>
            <span style="color: rgba(0, 0, 255, 1)">const</span> { to, cmd, data } = event.data ||<span style="color: rgba(0, 0, 0, 1)"> {};
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!to || !<span style="color: rgba(0, 0, 0, 1)">cmd) {
                console.warn(</span>'中转消息缺少目标标识或指令:'<span style="color: rgba(0, 0, 0, 1)">, event.data);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第三步:根据目标标识中转消息</span>
            let targetWindow = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
            let targetOrigin </span>= ''<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (to === 'iframeB' &amp;&amp; event.origin === 'http://child.example.com:8081'<span style="color: rgba(0, 0, 0, 1)">) {
                targetWindow </span>=<span style="color: rgba(0, 0, 0, 1)"> iframeB;
                targetOrigin </span>= 'http://another.example.com:8082'<span style="color: rgba(0, 0, 0, 1)">;
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (to === 'iframeA' &amp;&amp; event.origin === 'http://another.example.com:8082'<span style="color: rgba(0, 0, 0, 1)">) {
                targetWindow </span>=<span style="color: rgba(0, 0, 0, 1)"> iframeA;
                targetOrigin </span>= 'http://child.example.com:8081'<span style="color: rgba(0, 0, 0, 1)">;
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                logTransfer(`无效的中转请求:从 ${event.origin} 发送到 ${to}`);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行中转</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (targetWindow) {
                targetWindow.postMessage({
                  from: event.origin,
                  cmd,
                  data
                }, targetOrigin);
                logTransfer(`已将【${cmd}】指令从 ${event.origin} 中转到 ${targetOrigin}`);
            } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                logTransfer(`目标窗口未加载完成:${to}`);
            }
      }

      function logTransfer(content) {
            transferLog.innerHTML </span>+= `[${<span style="color: rgba(0, 0, 255, 1)">new</span> Date().toLocaleTimeString()}] ${content}&lt;br&gt;<span style="color: rgba(0, 0, 0, 1)">`;
            transferLog.scrollTop </span>=<span style="color: rgba(0, 0, 0, 1)"> transferLog.scrollHeight;
      }
    </span>&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p>&nbsp;</p>
<h4 class="heading-4 ace-line old-record-id-X6PNfHFbDdCvuxcUPCKL3mpcQ2S">2. 兄弟页面 A(发送方):http://child.example.com:8081/brother-a.html</h4>
<div class="cnblogs_code">
<pre>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;兄弟页面 A&lt;/title&gt;
    &lt;style&gt;<span style="color: rgba(0, 0, 0, 1)">
      .container { margin: 20px; }
    </span>&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="container"&gt;
      &lt;h1&gt;兄弟页面 A(child.example.com:8081)&lt;/h1&gt;
      &lt;button onclick="sendToB()"&gt;向页面 B 发送数据&lt;/button&gt;
      &lt;div id="receiveBox" style="margin-top: 20px; padding: 10px; border: 1px solid #009933;"&gt;&lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;
      <span style="color: rgba(0, 0, 255, 1)">const</span> receiveBox = document.getElementById('receiveBox'<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">const</span> parentOrigin = 'http://parent.example.com:8080'<span style="color: rgba(0, 0, 0, 1)">;

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向页面 B 发送消息(通过父页面中转)</span>
<span style="color: rgba(0, 0, 0, 1)">      function sendToB() {
            window.parent.postMessage({
                to: </span>'iframeB', <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关键:指定目标兄弟页面</span>
                cmd: 'syncData'<span style="color: rgba(0, 0, 0, 1)">,
                data: {
                  key: </span>'theme'<span style="color: rgba(0, 0, 0, 1)">,
                  value: </span>'dark' <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 模拟同步主题状态</span>
<span style="color: rgba(0, 0, 0, 1)">                }
            }, parentOrigin);
      }

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 监听页面 B 的回传消息</span>
      window.addEventListener('message'<span style="color: rgba(0, 0, 0, 1)">, function(event) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.origin !== parentOrigin) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">const</span> { from, cmd, data } =<span style="color: rgba(0, 0, 0, 1)"> event.data;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cmd === 'syncAck'<span style="color: rgba(0, 0, 0, 1)">) {
                receiveBox.innerText </span>=<span style="color: rgba(0, 0, 0, 1)"> `收到页面 B 确认:${data.msg},来自 ${from}`;
            }
      });
    </span>&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p>&nbsp;</p>
<h4 class="heading-4 ace-line old-record-id-YbEQfrjjndz0WQcb63CgYAvhwsJ">3. 兄弟页面 B(接收方 + 回传):http://another.example.com:8082/brother-b.html</h4>
<div class="cnblogs_code">
<pre>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;兄弟页面 B&lt;/title&gt;
    &lt;style&gt;<span style="color: rgba(0, 0, 0, 1)">
      .container { margin: 20px; }
    </span>&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>="container"&gt;
      &lt;h1&gt;兄弟页面 B(another.example.com:8082)&lt;/h1&gt;
      &lt;div id="receiveBox" style="margin-bottom: 20px; padding: 10px; border: 1px solid #009933;"&gt;&lt;/div&gt;
      &lt;button onclick="sendAckToA()"&gt;向页面 A 发送确认&lt;/button&gt;
    &lt;/div&gt;

    &lt;script&gt;
      <span style="color: rgba(0, 0, 255, 1)">const</span> receiveBox = document.getElementById('receiveBox'<span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">const</span> parentOrigin = 'http://parent.example.com:8080'<span style="color: rgba(0, 0, 0, 1)">;
      let lastFrom </span>= ''; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 存储发送方源,用于回传

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 监听父页面的中转消息</span>
      window.addEventListener('message'<span style="color: rgba(0, 0, 0, 1)">, function(event) {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (event.origin !== parentOrigin) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">const</span> { from, cmd, data } =<span style="color: rgba(0, 0, 0, 1)"> event.data;
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cmd === 'syncData'<span style="color: rgba(0, 0, 0, 1)">) {
                lastFrom </span>=<span style="color: rgba(0, 0, 0, 1)"> from;
                receiveBox.innerText </span>= `收到页面 A 数据:${data.key}=<span style="color: rgba(0, 0, 0, 1)">${data.value},来自 ${from}`;
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 业务逻辑:切换暗黑主题</span>
                document.body.style.backgroundColor = '#333'<span style="color: rgba(0, 0, 0, 1)">;
                document.body.style.color </span>= '#fff'<span style="color: rgba(0, 0, 0, 1)">;
            }
      });

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向页面 A 发送确认(通过父页面中转)</span>
<span style="color: rgba(0, 0, 0, 1)">      function sendAckToA() {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">lastFrom) {
                alert(</span>'尚未收到页面 A 的数据!'<span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
            }
            window.parent.postMessage({
                to: </span>'iframeA'<span style="color: rgba(0, 0, 0, 1)">,
                cmd: </span>'syncAck'<span style="color: rgba(0, 0, 0, 1)">,
                data: {
                  msg: </span>'主题已同步完成'<span style="color: rgba(0, 0, 0, 1)">
                }
            }, parentOrigin);
      }
    </span>&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p>&nbsp;</p>
<h4 class="heading-4 ace-line old-record-id-EVKgfQ320dWN2NccqEPOflFbw29">场景三关键要点</h4>
<ol class="list-number1" start="1">
<li class="ace-line ace-line old-record-id-Uj6lfwewWd5fHwcY8ZzOfHhlPeV" data-list="number"><strong>中转核心逻辑</strong>:父页面作为“消息枢纽”,通过 <code>to</code> 字段识别目标兄弟页面,完成消息转发;</li>
<li class="ace-line ace-line old-record-id-XY74fD3f8d56vDc3ApuEsuZeUKI" data-list="number"><strong>双向可信校验</strong>:父页面校验发送方是否在可信列表,子页面校验中转消息是否来自父页面;</li>
<li class="ace-line ace-line old-record-id-OGmkf23AUdcqxxcnmkMNwaQdg0N" data-list="number"><strong>业务标识</strong>:通过 <code>from</code> 字段记录发送方,实现兄弟页面的双向回传。</li>
</ol>
<h2 class="heading-2 ace-line old-record-id-CyELfHOEgdmrEYcok9UKmpsgbPE">四、企业级安全规范与避坑指南</h2>
<div class="ace-line ace-line old-record-id-JKmQf75AodZK3pcjUuBprXYbAH5"><code>postMessage</code> 是“双刃剑”,若使用不当,会带来<strong>跨站脚本攻击(XSS)</strong>、<strong>数据泄露</strong>等安全风险。结合大厂实战经验,以下是必须遵守的安全规范和避坑要点。</div>
<h3 class="heading-3 ace-line old-record-id-Lpw0f2gmadPcsvchmpOiR2Dc0jR">1. 核心安全准则(必须严格执行)</h3>
<div>
<table class="ace-table" data-ace-table-col-widths="200;200;200"><colgroup><col width="200"><col width="200"><col width="200"></colgroup>
<thead>
<tr><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-SvJFfYd5ZduDGtcwBsvsUqveqxV">安全环节</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-IQ6zfGURyd1UxRcESr8LRcSf7LX">核心要求</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-IYurfbowedeDkTc2FZwRqKK3Av0">禁止行为</div>
</th></tr>
</thead>
<tbody>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-HEzufM2Esdhpifc7wlwWesewmL3">发送端</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-Pv4QfdjoZdtXCbcYGs9AADnf2gJ">始终指定<strong>明确的 targetOrigin</strong>(协议+域名+端口)</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-QI8ofjLSndF0CxcVQApoQpSegr6">生产环境使用 <code>*</code> 通配符</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-Ex9KfkQapdEEXtcioUhGl1ZcmDe">接收端</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-KO72fJeXVd7kr7cznS1N85sgDLH">第一步校验 <code>event.origin</code>(仅接收可信源)</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-CsQYflsHJddtRFcV8ZA4M9obEBl">信任 <code>event.data</code> 中的“sender”字段,或跳过源校验</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-DCi2ffX0vdjCLUcEVhElJrZgImu">数据校验</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-MaGlftUtKdOrWgcb4PW3yqjciQE">校验 <code>event.data</code> 的类型、格式、业务指令</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-Ugz7fdlu2dEA5Nc9jOWqzuGchxn">直接执行 <code>eval(event.data)</code>,或直接将数据插入 DOM</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-VxgSfTAD0dRVTCcqArSYaedgQzF">敏感数据</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-CmPQfgGxYdYkXrcGmSYXPZshmzh">避免发送明文敏感数据,必要时加密传输</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-M2gafLdChdQXwKcJXIe6gbvhRJ2">发送密码、银行卡号等明文敏感信息</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="ace-line ace-line old-record-id-JM0qfava8dGoYVct3lQqiuld1H5"><strong>关键原理</strong>:<code>event.origin</code> 是浏览器强制注入的、不可篡改的源标识,是唯一可信的跨域身份凭证,而 <code>event.data</code> 可被恶意构造,绝对不能作为身份校验依据。</div>
<h3 class="heading-3 ace-line old-record-id-E62oftGbQddA8McXi1cKsvyhbXX">2. 常见避坑要点</h3>
<h4 class="heading-4 ace-line old-record-id-EV8YfBgGrdXzpxcwioGUKnscbDp">坑点 1:iframe 跨域时无法获取 contentDocument</h4>
<div class="ace-line ace-line old-record-id-UQj5f4LqVdejJocNUx9k42Gb2AU">很多开发者会尝试通过 <code>iframe.contentDocument</code> 获取跨域 iframe 的 DOM,这会被浏览器拦截,抛出“跨域访问被拒绝”的错误。<strong>解决方案</strong>:仅通过 <code>postMessage</code> 传递数据,不直接操作跨域 DOM。</div>
<h4 class="heading-4 ace-line old-record-id-XkyYfGCmGdqmXbc99lofheteGuX">坑点 2:消息事件监听重复绑定</h4>
<div class="ace-line ace-line old-record-id-U7h5fYf4qdnUfFcLX6xT97ZYSAU">多次执行 <code>window.addEventListener('message', ...)</code> 会导致同一消息被处理多次。<strong>解决方案</strong>:将监听逻辑写在页面初始化时,或使用“事件委托”,页面卸载时必须移除监听。</div>
<h4 class="heading-4 ace-line old-record-id-PUmqf5M99dBtZ4cUOTlDtxXgXIC">坑点 3:发送大数据导致性能问题</h4>
<div class="ace-line ace-line old-record-id-DQ5FfRS4Ndhl5HcvMUj76nW5xBo"><code>postMessage</code> 适合传递小体积的业务数据(如指令、状态),若发送超过 10MB 的数据(如大文件、大量列表),会导致页面卡顿、传输失败。<strong>解决方案</strong>:大数据传输使用 CORS 接口或分片上传,<code>postMessage</code> 仅传递传输状态。</div>
<h4 class="heading-4 ace-line old-record-id-XJpdf2u4Ydt2V4cFHSHe3EAgrfz">坑点 4:忽略子页面跳转后的源变化</h4>
<div class="ace-line ace-line old-record-id-Qj47fCkhvd8js7c6jrzZvPsdOK9">若子页面通过 <code>location.href</code> 跳转到其他域名,<code>event.origin</code> 会变为新域名,此时父页面的消息会发送失败。<strong>解决方案</strong>:在子页面跳转前,通过 <code>postMessage</code> 通知父页面,更新目标源;或在父页面监听 iframe 的 <code>onload</code> 事件,重新校验源。</div>
<h3 class="heading-3 ace-line old-record-id-KWxUfIeLMd4rpNc2obhK6kB4slB">3. 进阶安全优化(大厂实战方案)</h3>
<ol class="list-number1" start="1">
<li class="ace-line ace-line old-record-id-ZAaJfYEQeduYo1cnArlcagbgg6H" data-list="number"><strong>消息签名机制</strong>:发送方对消息数据进行加密签名(如使用 HMAC),接收方校验签名,防止消息被篡改;</li>
<li class="ace-line ace-line old-record-id-Yh7EfRqF9dfGOCcwGKScKZygg1T" data-list="number"><strong>白名单动态管理</strong>:将可信源白名单存储在服务器,通过接口动态获取,避免硬编码;</li>
<li class="ace-line ace-line old-record-id-ZXVzfXwIBdUdqOcQeI1Za2BhwfY" data-list="number"><strong>指令白名单</strong>:接收方仅处理预定义的业务指令(如 <code>userLogin</code>、<code>paySuccess</code>),拒绝未知指令;</li>
<li class="ace-line ace-line old-record-id-LorBfcYDidHU02c34NvyAzhdAA4" data-list="number"><strong>日志记录</strong>:对所有跨域消息的发送、接收、处理过程进行日志记录,便于故障排查和安全审计。</li>
</ol>
<h2 class="heading-2 ace-line old-record-id-DKIYfBzV3dffbjct8XTZrLDgPgh">五、postMessage 与其他跨域方案的对比</h2>
<div class="ace-line ace-line old-record-id-Ot9cfRkKIdW6B7ctzBxsLwPfRBx">为了让你在实际开发中选择最合适的方案,以下是 <code>postMessage</code> 与其他主流跨域方案的对比:</div>
<div>
<table class="ace-table" data-ace-table-col-widths="200;200;200;200"><colgroup><col width="200"><col width="200"><col width="200"><col width="200"></colgroup>
<thead>
<tr><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-XXALfYmSld54zxcYNI2ya9gbcK9">方案</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-WtqFfJ60bd2QiKcEU7E1JWJhF03">核心优势</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-Xos6f49JTdNYLzcMo86l91bg2rE">局限性</div>
</th><th rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-BW6UfPr77dxl6TcV4F0MmEhgrYU">适用场景</div>
</th></tr>
</thead>
<tbody>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-ZbXOfIUJLdtRx7c5oanpXLccQhv">postMessage</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-KXjRf9mfadvwtqcV4g9YMIFb3Lf">1. 无需服务器参与,前端独立实现;&lt;br&gt;2. 支持双向通信;&lt;br&gt;3. 兼容性好(IE8+ 支持)</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-EcAXfjKHhddjxMcHh4VDHTJbLeV">1. 需手动管理窗口引用;&lt;br&gt;2. 存在安全风险,需严格校验</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-IVn1fkYDFdJydeceZBP1t97eRwn">1. iframe 父子/兄弟通信;&lt;br&gt;2. window.open 新窗口通信;&lt;br&gt;3. 微前端跨应用通信</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-RZqrftIGHdcwdYcu4jYbJyNYi34">CORS</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-OGLLf7SsCdLH1xcJ1x7w39pdiSp">1. 标准的跨域请求方案;&lt;br&gt;2. 支持所有 HTTP 请求方法</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-DdAIfQHfxdG7wWc8w57Qpr0cnkC">1. 需服务器配置;&lt;br&gt;2. 仅适用于客户端与服务器通信</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-QYh1fYMY9drdVpc8xKsT6o3dBjk">前端向跨域服务器发送 AJAX 请求</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-BPmDfX6ewd0jE2coX3w08YFhyzG">JSONP</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-W5MXfIxG1dJmKdcGJC8tJ1woJZ5">1. 兼容性极好(支持老式浏览器)</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-BQPcfmEzldBAEycCmLbfkh7eHzn">1. 仅支持 GET 请求;&lt;br&gt;2. 存在 XSS 风险</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-Qx8ffvybwdPOFucRQdLB8AYgGBJ">老式浏览器的跨域数据请求</div>
</td>
</tr>
<tr>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-DmFFf0Scid7lpYcqpoxErrhbth9">BroadcastChannel</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-FNzIfGJhFdEabdcy7BrGn28s6zz">1. 支持同源多标签页广播通信;&lt;br&gt;2. 无需管理窗口引用</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-XK7sfrFzvdQRMtcQSQQfcZwgXqw">1. 不支持跨域;&lt;br&gt;2. IE 不支持</div>
</td>
<td rowspan="1" colspan="1">
<div class="ace-line ace-line old-record-id-JEsBfEiVGd3He9cbOASOQWHecfr">同源多标签页状态同步(如登录状态、主题切换)</div>
</td>
</tr>
</tbody>
</table>
</div>
<h2 class="heading-2 ace-line old-record-id-VGADfjUUmdU6tQcLDzrZxiZdZgW">六、总结</h2>
<div class="ace-line ace-line old-record-id-W3QRf9NOidGdl3cLXuMcXJ4b5pV"><code>postMessage</code> 作为 HTML5 解决跨域通信的核心 API,通过“消息事件机制”和“源校验机制”,既打破了同源策略的限制,又保障了通信安全。它的核心价值在于<strong>前端独立实现双向跨域通信</strong>,无需依赖服务器中转,是 iframe 交互、新窗口通信、微前端架构中的必备技术。</div>
<div class="ace-line ace-line old-record-id-PLhQfKUxWdTvdnc6EaqJo8dbO5L">掌握 <code>postMessage</code> 的关键,不在于记住语法,而在于<strong>理解安全校验的核心逻辑</strong>:发送端指定明确的 <code>targetOrigin</code>,接收端严格校验 <code>event.origin</code> 和消息格式。同时,要避开“重复绑定监听”“操作跨域 DOM”等常见坑点,结合企业级安全规范(如消息签名、日志记录),才能在实际开发中安全、高效地使用这项技术。</div>
<div class="ace-line ace-line old-record-id-LIrCfWFfvdHhh6cj0PwWHongtxK">随着前端技术的发展,微前端、跨应用交互的需求越来越多,<code>postMessage</code> 依然是解决这类问题的“黄金方案”。希望本文的实战代码和安全指南,能帮你彻底打破同源枷锁,从容应对各类跨域通信场景。</div>
</div><br><br>
来源:https://www.cnblogs.com/qishun/p/19659956
頁: [1]
查看完整版本: 打破同源枷锁:深入理解 postMessage 跨域通信机制