最近要写的一个页面内嵌了一个iframe,引入的页面是其他域名的页面。复页面需要获取子页面iframe里面的信息,这就需要子页面像父页面传值,首先想到了用iframe间的通信。
// 子页面
<div id='child'>i am a child</div>
<script>
window.onload = function() {
var parentDom = parent.window.document.getElementById('parent');
setTimeout(() => {
parentDom.innerHTML = 'chang parent'
}, 1000);
}
</script>
// 父页面
<h1 id="parent">i am parent</h1>
<iframe id='iframe' src="./child.html" frameborder="0"></iframe>
<script>
window.onload = function() {
var dom = document.getElementById('iframe');
var child = dom.contentWindow.document.getElementById('child');
child.innerHTML =' hello world'
window.frames[0].document.getElementById('child').innerHTML = 'child2'
}
</script>
以上代码块是父子页面互相通信改变值的demo。
但是以上通信存在一个问题,如果父子页面属于不同的域,由于浏览器的同源政策,相互通信则比较麻烦。虽然可以在子页面中嵌入一个代理页面作为中转站通信,但是处理起来比较麻烦。经请教可以用H5的API postMessage进行不同域页面间的通信,对此做一个总结。
什么是postMessage
看CDN的介绍:window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。 所以,postMessage可以很轻松的帮我们解决跨域的问题。
语法
1.发送postMessage消息:
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow是需要发送消息的源窗口。源窗口可以是页面中的iframe窗口,也可以是通过window.open()打开的窗口。或者是命名过或数值索引的window.frames
// 页面中的iframe窗口
var iframe = document.getElementById('my-iframe');
var win = iframe.documentWindow;
// 弹窗
var win = window.open();
// iframe窗口的父窗口
var win = window.parent;
获取源窗口后,即可向目标窗口发送数据message,其中message会被结构化克隆算法序列化,也就是我们无需手动序列化数据传输到目标窗口。
targetOrigin用以指定哪些窗口可以接收消息,可以是字符串"*"(表示无限制)或者一个URI。如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。因此如果我们知道需要向哪些窗口传输消息时,一定要指定targetOrigin,避免数据泄露。
window.parent.postMessage('i am click!', 'http://localhost:8081/parent3.html');
2.接收postMessage消息
在目标窗口注册message事件,绑定监听函数,监听函数的参数内附有传递来的消息。
window.addEventListener('message',function(e){
alert(e.data);
},false);
其中e的内容如下:
其中origin是消息发送窗口的origin,我们可以在注册的回调函数内origin进行判断,然后决定是否做出回应。data是发送的数据内容,source是消息发送窗口的window对象引用。
因此在不同域中,嵌入的子页面iframe向父窗口发送消息具体demo如下。
// http://localhost:8081/parent3.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>
parent
</div>
<iframe src="http://local.study.baidu.com:8084/child3" frameborder="0"></iframe>
<script>
window.addEventListener('message',function(e){
alert(e.data);
},false);
</script>
</body>
</html>
// http://local.study.baidu.com:8084/child3.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>child3</div>
<button>hello world</button>
<script>
window.onload = function () {
var button = document.getElementsByTagName('button')[0];
var count = 1;
button.addEventListener('click', function() {
window.parent.postMessage('i am click!' + (++count), 'http://localhost:8081/parent3.html');
});
}
</script>
</body>
</html>
有一些我们需要注意的地方:
1.postMessage使用不当,会导致一些安全问题,需要我们注意:用于接收消息的任何事件监听器必须首先使用origin和source属性来检查消息的发送者的身份。 这不能低估:无法检查origin和source属性会导致跨站点脚本攻击。
2.如果不是使用 window.open() 打开的页面或者 iframe 嵌入的页面,无法使用 window.postMessage() 进行跨域通信的。CDN对此说的很明白,从广义上讲,一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener),然后在窗口上调用 targetWindow.postMessage() 方法分发一个 MessageEvent 消息。 两个不同页面是无法获取对方的window的,因此无法使用postMessage通信。
3.另外需要注意的是pageA使用window.open()打开的pageB,是无法主动向pageA发送消息的,必须先接收到 PageA 页面发送过来的 message 然后再通过 event.source 发送给 PageA,此时的 window 就是 event.source,即 PageA 的 window。