前端跨域通信,如何利用postMessage实现跨域页面通信?
发布于 作者:苏南大叔 来源:程序如此灵动~本文依然说一下同设备不同页面跨域通信的解决方案,这个方案暂时称之为postMessage
方案。不过和上一篇文章里面的频道广播的.postMessage()
方法是不同的,其数据发送主体不一样。本文中的.postMessage()
的主体是浏览器的窗口对象,而且这个对象的接收者和发送者身份切换,也是比较令人费解的。
这里是苏南大叔的程序如此灵动博客,记录苏南大叔和计算机代码的故事。本文描述前端技术里面不同页面通信的技术手段之一,本文中的postMessage
方案,操作主体是浏览器的窗口对象,同时可以解决同设备不同页面的跨域问题。本文测试环境:谷歌浏览器@110.0.5481.77
。
页面间关系
和频道广播的方案所不同的一点是:本文的方案中,页面之间的关系非常重要。页面之间必须主动知道彼此的存在,否则无法通信。彼此知道的方式是:页面之间是相互打开的,那么就建立了页面之间的相互关系,进而为数据通信打下坚实的基础。
常见的调用方式,有页面一iframe页面二,或者页面一打开页面二。总结如下:
关系 | 调用方式 | 发送消息调用主体 | 反向发送消息 |
---|---|---|---|
iframe | iframe id='f' | document.getElementById("f").contentWindow. | window.top. |
opener | a = window.open('') | a. | window.opener. |
对于window.open()
的情况,存在着被浏览器拦截弹窗的可能性,应用场景相对有限。
发送消息
发送消息的代码是:
被发送消息的页面所在的window对象.postMessage(消息字符串或对象,域限制短语)
这个是很令人费解的发送方式:
发送者js
代码” => 被发送者所在的浏览器对象 => .postMessage()
=> 消息对象(字符串或者js对象) => 然后表明一下消息传递的域限制
- 被发送者所在的浏览器对象,这个就是 发送者页面 如何 找到 被发送者的页面,可能的方式是:通过
iframe
的contentWindow
,或者window.open
的返回id
。 - 域限制,这个默认情况就是
/
,也就是说相同域名下,为了跨域,可以被这个参数改写成*
。或者传递一个域名地址。
接收消息
接收消息的代码,这个就没有那么令人费解了,直接监听window
的message
事件即可。所以,写法有下面两种:
window.onmessage = function(event) {
console.log(event.data);
}
或者
window.addEventListener('message', function(event) {
console.log(event.data);
},false)
目标 | 代码 |
---|---|
发送者是谁 | event.origin |
发送的消息是什么 | event.data |
至于最后一个参数useCapture
,是用于控制事件传递的方向的。参考文章:
域限制
接收者消息事件中有个event.origin
,发送者的postMessage()
的第二个参数是个域限制。两者有关,但是也并不完全一致。
首先,发送者有域限制的权利,保证非法的接收者拿不到数据:
发送者(虽然委托的是接收者所在的浏览器对象发送的)需要指定消息的接收者的域关系。不填写的话,就默认是/
,这个比较好理解就是同域的意思。填写为*
,就是表示跨域。如果写上精确的域名(比如``),那么就需要接收者页面自己做判别了。
.postMessage("消息")
.postMessage({msg:"消息",code:1000})
.postMessage("消息", "/")
.postMessage("消息", "*")
.postMessage("消息", "http://localhost/")
其次,接收者也有拒绝消息的权利,通过判断event.origin
来自行判断是否接收当前消息。比如:
window.addEventListener('message', function(event) {
if (event.origin.includes("http://localhost/")) {
// ...
}
});
测试代码
页面一:
<h1>页面一</h1>
<div id="message"></div>
<h2>页面一两种方式打开了页面二,两个页面二都返回了数据。</h2>
<iframe id="child" src="http://a/postmessage/2.html" style="width:500px;"></iframe>
<script>
window.onload = function () {
var target = "";
target = document.getElementById('child').contentWindow;
target.postMessage("苏南大叔", "*");
var target = window.open("http://a/postmessage/2.html");
setTimeout(function () {
target.postMessage({msg:"sunan大叔",coin:888}, "http://a/postmessage/");
}, 2000);
};
window.addEventListener(
"message",
function (event) {
document.getElementById("message").innerHTML += event.origin + "=》" + event.data + "<br/>";
}
);
window.onmessage = function(){
document.getElementById("message").innerHTML += event.origin + "=》》" + event.data + "<br/>";
}
</script>
页面二:
<h1>页面二</h1>
<div id="message"></div>
<script>
window.addEventListener(
"message",
function (event) {
console.log(event);
if (event.origin.includes("http://localhost")) {
if (window.top!=window.self) {
document.getElementById("message").innerHTML += "iframe方式," + event.origin + " :" + event.data;
window.top.postMessage(
"页面2返回消息【iframe】","http://localhost/"
);
} else if (window.opener != null) {
document.getElementById("message").innerHTML += "opener方式," + event.origin + " :" + event.data.msg + "," + event.data.coin;
window.opener.postMessage("页面2返回消息【opener】," + event.data.coin, "*");
}
}
}
);
</script>
- 支持本地
file://
。 - 两者之间可以在不同的域名下面,代码中可以指定作用域。
- 页面间可以是
iframe
关系,也可以是opener
关系。在对应的代码中有区分。
相关文章
- https://newsn.net/say/electron-webview-promise.html
- https://newsn.net/say/electron-websecurity.html
- https://newsn.net/say/window-name-msg.html
- https://newsn.net/say/html5-server-sent-events.html
总结
本文描述的方案,就是数据发送接收主体有些混乱,页面之间的关系必须理清。同时,记得指明数据归属的域。域的问题,可能面临两个:一是数据不能发送出去(浏览器的安全策略层面),二是数据发送到了错误的域上(代码的数据安全逻辑)。
代码中的.postMessage
的主体,更像是个第三方委托发送,如果增加个第三方委托的概念的话,这个发送关系就比较好理解了。单纯的理解为发送方和接收方的话,发送方的主体则是有些令人费解的。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。