在业务中,有一种开发形式为:多个子系统集成为一个父系统。每个系统之间都管理着不同的业务与逻辑,他们是互不干涉的。
那么,我们如何在父系统页面中操作子系统中的内容呢?
首先通过vite
初始化两个项目。
父系统端口号为3001,子系统端口号为3000。
然后在父系统页面中通过一个iframe
标签,打开子系统页面:
<div class="iframe" v-if="show"><iframe :src="`http://localhost:3001/#/?picUrl=${picUrl}`" frameborder="0"style="width:100%;height:100%"></iframe>
</div>
可以看到在父系统页面,成功打开了子系统页面内容。
postmessage通信
在web项目中通过iframe嵌入另一个第三方web项目,第三方web项目里点击某个按钮要实时调用web项目的全局函数打开某个全局弹窗或者进行路由跳转,这时候两个项目存在了数据交互,显然违反了同源策略,在HTML5标准引入的window对象下的postMessage方法,可以允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain
设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制。
从广义上讲,一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener
),然后在窗口上调用 targetWindow.postMessage()
方法分发一个 MessageEvent
消息。接收消息的窗口可以根据需要自由处理此事件 (en-US)。传递给 window.postMessage() 的参数(比如 message )将通过消息事件对象暴露给接收消息的窗口。
语法
otherWindow.postMessage(message, targetOrigin, [transfer]);
- 参数
- otherWindow:其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行window.open返回的窗口对象。
- message:将要发送到其他 window 的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。
- targetOrigin:通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个 URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口。
- transfer(可选):是一串和 message 同时传递的
Transferable
对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
简单用法
首先看看3001端口的页面,看看他是如何向调用者3000端口页面发送信息的。
localhost:3001
<template>
<div>子系统的某个页面<div @click="toFather" class="btn">操作父系统中的数据</div>
</div>
</template><script setup>
import {ref, onMounted} from 'vue'const toFather = () => {// 调用postMessage方法,第一个参数是待传递的数据,第二个参数是 origin 源数据parent.postMessage({msg: "黑猫几绛"}, "*")}
</script>
这里的parent
看起来有些突兀,进入lib.dom.d.ts
文件下看看他的定义:
/** Refers to either the parent WindowProxy, or itself. It can rarely be null e.g. for contentWindow of an iframe that is already removed from the parent. */
declare var parent: WindowProxy;
/**
可以看到parent在这里表示的就是一个Window的代理对象,这一句话也可以写为:
window.parent.postMessage({msg: "黑猫几绛"}, "*")
localhost:3000
再来看看调用者页面是如何接收消息的:
<script setup>import {ref, onMounted} from 'vue'const receiveMessage = (event) => {console.log(event.data.msg); // 黑猫几绛}onMounted(()=>{window.addEventListener('message',receiveMessage, false)})
</script>
- 在页面挂载的时候,通过
addEventListener
监听message事件 - 具体的监听方法会收到一个event对象参数,可以接收被调用者页面传来的数据
基本思路就是这样,现在在页面中来看看最终效果:
扩展
origin判断
如果不希望从其他网站接收 message,不要为 message 事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题。
在上面的图中可以看到,消息对象里面存在origin
数据来存放消息来源信息,因此我们可以根据它来进行来源的判断,毕竟不能监听那些恶意网站传递过来的信息。
window.addEventListener("message", receiveMessage, false);function receiveMessage(event) {let origin = event.origin || event.originalEvent.origin;// 如果不是3001端口页面传递过来的消息,调用者就不处理这些消息if (origin !== "http://localhost:3001")return;// ...
}
注意:这个 origin 不能保证是该窗口的当前或未来 origin,因为 postMessage 被调用后可能被导航到不同的位置。
如果确实希望从其他网站接收 message,请始终使用 origin 和 source 属性验证发件人的身份。 任何窗口都可以向任何其他窗口发送消息,并且页面不能保证未知发件人不会发送恶意消息。
即使是验证身份后,也仍然应该始终验证接收到的消息的语法。 否则,信任只发送受信任邮件的网站中的安全漏洞可能会在网站中打开跨网站脚本漏洞。