记录postMessage的详细使用

前言

咋一看一个很简单的API,只有当自己真正的使用过,才知道里面的坑。
使用window.postMessage可以帮助我们进行安全的跨源通信。
基本原理是通过postMessage来发送跨文档信息,使用message来进行监听,当收到跨文档信息后,会触发message事件

语法

targetWindow.postMessage(message, targetOrigin, [transfer]);

targetWindow:
目标窗口的引用,比如执行window.open返回的窗口对象、打开当前窗口的引用window.opener、iframe的contentWindow属性,或者是命名过或数值索引的window.frames
message:
发送给目标window的数据
targetOrigin:
指定接收消息事件的窗口,其值可以是一个URI或者字符串 "*"(表示无限制)

如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是*。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。

transfer: 可选(本文对该项不做解释)
是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

使用

本文使用VScode的Live Server启动的本地服务

自己给自己发送消息

当然这种方式没什么用处,只是在这里讲解一下onmessage事件的event对象的参数

// http://127.0.0.1:5500/A.html
const msg = {
    name: "mayoha"
  }
window.postMessage(msg, "http://127.0.0.1:5500")
window.addEventListener("message", (e) => {
  console.log(e.data)
  console.log(e.origin)
  console.log(e.source)
})
复制代码

onmessage事件处理程序的event对象包含了三方面重要信息
data: 作为第一个参数传递给postMessage的数据
origin: 发送消息的文档源,我们可以根据origin来确保预期的发送者
source: 发送消息的window代理对象

A打开B,B发送数据给A

// A.html
window.addEventListener("message", (e) => {
  console.log(e.data)
  console.log(e.origin)
  console.log(e.source)
})
// 自动调用open会被浏览器拦截,需要手动取消拦截
// 或者添加一个点击事件,通过手动调用来打开新窗口
window.open("http://127.0.0.1:5501/B.html", "B")
复制代码
// B.html
const msg = {
  name: "B"
}
const opener = window.opener
opener.postMessage(msg, "http://127.0.0.1:5500")
// 这里讲一下targetWindow与URI之间的联系
// URI必须是opener窗口所在在的URI
// 意思就是说,如果是从 http://127.0.0.1:5500 中的某个页面将B页面打开,那么就能成功发送跨文档信息
// 如果讲此处的URI换成"*",就意味着任何网页打开B页面,都能收到B页面传输的信息
复制代码

A打开B并且发送数据给B

// A.html
<button onclick="openB()">open B</button>
const msg = {
    name: "A"
  }
function openB() {
  const open = window.open("http://127.0.0.1:5501/B.html", "B")
  setTimeout(() => {
    open.postMessage(msg, "http://127.0.0.1:5501")
  },1000)
}

// 此处你或许会感到疑问,为什么要在setTimeout里调用postMessage
// 因为调用window.open()方法以后,远程 URL 不会被立即载入,载入过程是异步的。
//(实际加载这个URL的时间推迟到当前脚本块执行结束之后。窗口的创建和相关资源的加载异步地进行。)
// 此处不用setTimeout就会出现B页面还没有打开,消息就已经发送,导致B无法收到消息
// 当然setTimeout并不是一个好方法,此处只是为了方便演示
复制代码
// B.html
window.addEventListener("message", (e) => {
  console.log(e.data)
  console.log(e.origin)
  console.log(e.source)
})
复制代码

取代setTimeout解决方案
思路:A页面打开B页面,当B页面加载后,B页面就会postMessage给A页面一个信息,当A页面收到信息之后再postMessage给B页面信息
欢迎大家提出更好的思路

// A.html
const msg = {
  name: "A"
}
var open = window.open("http://127.0.0.1:5501/B.html", "B")
function openB() {
  open = window.open("http://127.0.0.1:5501/B.html", "B")
}
window.addEventListener("message", (e) => {
  if(e.origin === "http://127.0.0.1:5501") {
    open.postMessage(msg, "http://127.0.0.1:5501")
  }
})
复制代码
// B.html
const opener = window.opener
opener.postMessage(msg, "http://127.0.0.1:5500")
window.addEventListener("message", (e) => {
  console.log(e.data)
  console.log(e.origin)
  console.log(e.source)
})
复制代码

在Iframe里的使用

父传子

// A.html (父)
<iframe src="http://127.0.0.1:5501/B.html" frameborder="1" id="Bframe"></iframe>
const msg = {
    name: "A"
  }
window.onload = () => {
  // 自动调用必须放在onload中,通过事件调用则不用
  // let frame = document.querySelector("#Bframe").contentWindow
  let frame = window.frames[0]
  frame.postMessage(msg, "http://127.0.0.1:5501")
}
复制代码
// B.html
window.addEventListener("message", (e) => {
  console.log(e.data)
  console.log(e.origin)
  console.log(e.source)
})
复制代码

子传父

// A.html (父)
<iframe src="http://127.0.0.1:5501/B.html" frameborder="1" id="Bframe"></iframe>
window.addEventListener("message", (e) => {
  console.log(e.data)
  console.log(e.origin)
  console.log(e.source)
})
复制代码
// B.html
const msg = {
  name: "B"
}
window.top.postMessage(msg, "http://127.0.0.1:5500")
复制代码

最后

如有错误,还请斧正

Guess you like

Origin juejin.im/post/7041363060522975246