使用postMessage进行跨域通信

我们知道两个域名不同,或同一域名中协议、子域名、端口号其中一个不同时都属于跨域,无论是在平时的开发中,还是在面试里,我们会经常遇见跨域的问题。而针对跨域的解决方案,我们可以使用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;
		//获取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);
}()

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

postMessage

兼容性

目前主流的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

因为线上我暂时无法提供另外一个域名,所以不能制作在线demo。如有需要,可下载文章中两个域名下a.html和b.html页面的 压缩包

评论功能已关闭。