我们知道两个域名不同,或同一域名中协议、子域名、端口号其中一个不同时都属于跨域,无论是在平时的开发中,还是在面试里,我们会经常遇见跨域的问题。而针对跨域的解决方案,我们可以使用jsonp,也可以设置window.name,还可以通过iframe等,但这些都不是本篇重点,本篇的重点是postMessage。

window.postMessage方法提供了一套规避跨域的控制机制。当 window.postMessage 被调用时,会给目标页面发送消息,然后在目标页面触发message事件,目标页面再获取消息,然后进行相应的DOM或其他操作,当然我们也可以反向操作。好了,我们先看下他们的语法。

对于发送消息的方法有:

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

otherWindow : 一个对其他窗口的引用。例如iframe 元素的 contentWindow 属性

message : 你要传递的数据,通常是json格式,你需要通过JSON.stringify把它转换为了字符串格式

targetOrigin : 获取页面所在的域,即目标域。你可以设置*,表示允许任何域名获取你的数据,但是为了防止恶意第三方的拦截,最好是设置一个具体的域名。

transfer : 可选参数,一般为空

而接收消息的事件则是:

window.addEventListener("message", fn , false);

fn : 这个函数主要用于处理获取的消息

如何应用

那么接下来,我们该如何操作呢? 首先你的准备两个具备跨域条件的域名,这里我在本地分别用wamp和百度的fis搭建了 http://127.0.0.1http://127.0.0.1:8080 两个本地服务环境,因为前者无端口号(即默认端口为80),而后者有(8080),所以也算跨域了。

然后我们在两个域名下分别准备两个页面,比如 http://127.0.0.1 下建立一个 a.html , http://127.0.0.1:8080 下则建立一个 b.html。然后我们通过在 a.html 加入一个iframe作为媒介,iframe的src为 b.html ,接着我们在a.html进行按钮发送消息,通过在目标页b.html中利用onmessage事件监听,将数据插入到b.html中。反之,我们也可以把 b.html 的数据,利用相同的方法在 a.html 页面获取。或许描述的有点繁琐,那么来看代码和示意图了解更多:

http://127.0.0.1 域名下的 a.html

<p><button id="receive-data-btn">获取跨域数据</button></p>
<iframe width="800" src="http://127.0.0.1:8080/b/b.html" id="receive-iframe"></iframe>

~function(){
var dataA = {
name : "yi-a",
age : "28-a",
tech : "web-a"
},
receiveDataBtn = document.getElementById("receive-data-btn"),
receiveIframe = document.getElementById("receive-iframe").contentWindow;
//获取iframe的window对象,而不是iframe这个dom元素。否则无法使用postMessage方法

function sendMsg(){
var dataStr = JSON.stringify(dataA);
receiveIframe.postMessage( dataStr , "http://127.0.0.1:8080" );
}

function getMsg(event){
if(event.origin !== 'http://127.0.0.1:8080') return;
var targetData = JSON.parse(event.data),
targetName = targetData.name,
targetAge = targetData.age,
targetTech = targetData.tech,
box = document.createElement("p"),
boxHtml = "这个页面名称是a.html,是属于域名http://127.0.0.1。从http://127.0.0.1:8080中b.html页面获取的数据为:<br/>";

boxHtml += " name: <strong style='color:red'>" + targetName + "</strong><br/>";
boxHtml += " age: <strong style='color:red'>" + targetAge + "</strong><br/>";
boxHtml += " tech: <strong style='color:red'>" + targetTech + "</strong>";
box.innerHTML = boxHtml;
document.body.appendChild(box);
}

receiveDataBtn.addEventListener( "click" , sendMsg ,false );

// message监听
window.addEventListener( "message" , getMsg ,false );

}()

http://127.0.0.1:8080 域名下的 b.html

<p>在iframe中:</p>
~function(){
var dataB = {
name : "yi-b",
age : "28-b",
tech : "web-b"
};

function getMsg (event){
if(event.origin != "http://127.0.0.1") return;
var targetData = JSON.parse(event.data),
targetName = targetData.name,
targetAge = targetData.age,
targetTech = targetData.tech,
box = document.createElement("p"),
boxHtml = "这个页面名称是b.html,是属于域名http://127.0.0.1:8080/。从http://127.0.0.1中a.html页面获取数据为:<br/>";

boxHtml += " name: <strong style='color:red'>" + targetName + "</strong><br/>";
boxHtml += " age: <strong style='color:red'>" + targetAge + "</strong><br/>";
boxHtml += " tech: <strong style='color:red'>" + targetTech + "</strong>";
box.innerHTML = boxHtml;
document.body.appendChild(box);

var dataStr = JSON.stringify(dataB);
event.source.postMessage(dataStr , event.origin);
}

// message监听
window.addEventListener("message", getMsg , false);
}()

最后点击获取跨域数据的按钮,你将看到:

兼容性

目前主流的chrome、firefox都支持,值得欣慰的是,虽然是html5的api,但ie8+也部分支持!

注意点:

  1. 在使用iframe作为媒介时,必须获取iframe的window对象,即本篇文章中的 iframe.contentWindow,而不是iframe这个dom元素,否则无法使用postMessage方法

  2. 在推送数据前,必须将json数据对象转换为字符串再postMessage,即本篇中先使用 JSON.stringify(json)将json数据转换为字符串 ,否则程序无法执行,报错为 Uncaught SyntaxError: Unexpected token o

  3. 虽然 http://127.0.0.1 默认也可以用localhost访问。但是在测试时,你必须使用 http://127.0.0.1 访问,因为在获取数据的页面存在一个 event.origin 的判断,如果你用的是localhost,那么 event.origin 永远也不会等于 http://127.0.0.1, 即程序会一直return