PostMessage

PostMessage

1. 概念:

window.postMessage是浏览器提供的跨窗口安全通信API——不管窗口之间是否同源、是弹窗还是iframe 嵌套,都能用它传数据。

  • 为什么需要它:同源策略的墙

    浏览器规定:不同源的窗口(协议+域名+端口任一不同)互相隔离——父页面不能读 iframe 的 DOM,iframe 也不能碰父页面的变量。

    1
    2
    页面A (http://a.com)  ←— 直接访问被拦 —→  iframe (http://b.com)
    SecurityError!

2. 实现方式:

  1. 发送方

    1
    targetWindow.postMessage(message, targetOrigin, [transfer]);
    参数 说明
    targetWindow 目标窗口引用:iframe.contentWindow、window.open() 返回值、window.parent、event.source
    message 要传的数据。可以是对象、数组、字符串等可结构化克隆的值(函数、DOM 节点不行)
    targetOrigin 安全核心。指定目标的 origin(如 http://b.com),浏览器只在目标窗口确实属于这个 origin 时才投递;不匹配则静默丢弃。特殊值 ‘*’ 表示不限(不安全,尽量别用)
    transfer 可选,Transferable 对象(如 ArrayBuffer),所有权转移
  2. 接收方

    1
    2
    3
    4
    5
    window.addEventListener('message', (e) => {
    e.data // 发来的数据
    e.origin // 发送方的 origin(协议+域名+端口)
    e.source // 发送方窗口的引用(用它回信)
    });

3. 示例:

  1. 父页面发:

    1
    2
    3
    4
    5
    const iframe = document.getElementById('child');
    iframe.contentWindow.postMessage(
    { type: 'HELLO', payload: '你好' },
    'http://b.com' // 目标必须精确是这个 origin
    );
  2. iframe 收 + 回信:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    window.addEventListener('message', (e) => {
    if (e.origin !== 'http://a.com') return; // ★ 校验来源

    console.log(e.data); // {type:'HELLO', payload:'你好'}

    e.source.postMessage( // e.source = 父页面窗口
    { type: 'HELLO_ACK', payload: '收到' },
    e.origin // ★ 回信目标用 e.origin,精确
    );
    });

4. 五个必须刻在脑子里的要点:

  1. targetOrigin 差一个字符就静默丢

    http://b.com vs http://b.com/ vs http://b.com:8080 互不相等。消息丢了不报错、不打日志。调试期可以临时用 ‘*’ 验证链路,确认后换回精确值。

  2. 接收方永远校验 e.origin

    postMessage 是公开信道,页面上任何脚本(包括嵌的第三方 iframe)都可能发消息过来。不校验 origin = 给陌生人开门:

    1
    2
    3
    4
    window.addEventListener('message', (e) => {
    if (e.origin !== 'http://expected.com') return; // 白名单
    // ...处理
    });
  3. 回信用 e.source + e.origin

    拿到消息后要回信,用事件自带的 e.source(发送方窗口引用)而不是自己再去 getElementById——嵌套 iframe 场景里你自己找的可能找错层,e.source 百分百准确:

    1
    e.source.postMessage(reply, e.origin);
  4. 消息是异步的,没有内置“请求-响应”配对

    postMessage 本质是发完即忘。要做“调用返回结果”,得自己实现关联机制——这就是自己增加 msgId 的作用:

    1
    2
    3
    4
    5
    // 发送方带唯一 ID
    postMessage({ msgId: 'm_123', code: '...' });
    // 接收方回信原样带回
    e.source.postMessage({ msgId: 'm_123', result: '...' });
    // 发送方按 msgId 找到对应的 pending Promise 来 resolve

    再加超时兜底,就是完整的异步调用封装。

  5. 数据经过结构化克隆,不是共享引用

    对象传过去是拷贝,对方改了不影响你这边的原对象。传不了函数、DOM、class 实例;JSON 能表达的都没问题。

5. 典型应用场景

场景 通信双方
嵌第三方页面 父页 ↔ iframe
window.open 弹窗与 opener 弹窗 ↔ window.opener
微前端(qiankun、iframe 方案) 主应用 ↔ 子应用
跨域文件上传(老方案 iframe hack) 隐藏 iframe ↔ 主页
Web Worker 通信(worker.postMessage,同款机制) 主线程 ↔ Worker

6. 常见坑速查

现象 原因
消息发出去没反应 targetOrigin 与目标实际 origin 不匹配(静默丢弃)
收到了但 origin 校验不过 白名单写的值与 e.origin 逐字符不一致(localhost≠127.0.0.1、带不带端口、结尾斜杠)
嵌套 iframe 发不到内层 浏览器插件、页面里其他脚本也在 postMessage,处理前判空 if (!msg || typeof msg !== ‘object’) return
收到一堆 null/字符串消息 隐藏 iframe ↔ 主页
数据传不过去报 DataCloneError 消息里有函数/DOM 节点,只能传可克隆的纯数据
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信