我们知道两个域名不同,或同一域名中协议、子域名、端口号其中一个不同时都属于跨域,无论是在平时的开发中,还是在面试里,我们会经常遇见跨域的问题。而针对跨域的解决方案,我们可以使用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.1 和 http://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; 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 ); 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: 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); } window.addEventListener("message", getMsg , false); }()
|
最后点击获取跨域数据的按钮,你将看到:
兼容性
目前主流的chrome、firefox都支持,值得欣慰的是,虽然是html5的api,但ie8+也部分支持!
注意点:
在使用iframe作为媒介时,必须获取iframe的window对象,即本篇文章中的 iframe.contentWindow,而不是iframe这个dom元素,否则无法使用postMessage方法
在推送数据前,必须将json数据对象转换为字符串再postMessage,即本篇中先使用 JSON.stringify(json)将json数据转换为字符串 ,否则程序无法执行,报错为 Uncaught SyntaxError: Unexpected token o
虽然 http://127.0.0.1 默认也可以用localhost访问。但是在测试时,你必须使用 http://127.0.0.1 访问,因为在获取数据的页面存在一个 event.origin 的判断,如果你用的是localhost,那么 event.origin 永远也不会等于 http://127.0.0.1, 即程序会一直return