前端遇到的跨域问题及解决方案二
解决不同域之间JS交互问题,有这么几种方法。
FIM – Fragment Identitier Messaging
不同的域之间,JavaScript只能做很有限的访问和操作,其实我们利用这些有限的访问权限就可以达到跨域通信的目的了。FIM (Fragment Identitier Messaging)就是在这个大前提下被发明的。父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL,URL有一部分被称为frag,就是#号及其后面的字符,它一般用于浏览器锚点定位,Server端并不关心这部分,应该说HTTP请求过程中不会携带frag,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录。FIM的原理就是改变URL的frag部分来进行双向通信。每个window通过改变其他window的location来发送消息,并通过监听自己的URL的变化来接收消息。
缺点
这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持onhashchange事件,需要轮询来获知URL的改变,最后,URL在浏览器下有长度限制,这个制约了每次传送的数据量。
举例子:
Cnblog.com下面的页面 parent.html 需要内嵌sina.com.cn下面的页面 child.html,要求二者可以通信,比如parent.html问Name,child回答Fany或者child主动提供页面的高度给parent
大致思路代码如下:
parent.html
<iframe id="iframeChild" src="…./child.html" width="100%" border="none"></iframe>
<script type="text/javascript"> var iChild=document.getElementById("iframeChild"); iChild.src="iframe-child.html#Height";//iframe-child.html#Name function checkHash() { var data = location.hash ? location.hash.substring(1) : ''; if (data) { //处理返回值 console.log(data); //iChild.style.height=data; location.hash=''; clearInterval(time1); } } var time1=setInterval(checkHash, 2000);
child.html
var Height=document.body.scrollHeight; function checkHash(){ var data = '',tHash=location.hash ? location.hash.substring(1) : ''; if(tHash){ switch(tHash){ case 'Name': data = "Fany";clearInterval(time1);break; case 'Height': data = Height;clearInterval(time1);break; default:break; } window.parent.location.hash=data; } else return; } var time1=setInterval(checkHash, 2000);
如果也牵扯到前进、后退历史按钮功能的话,这个方法有点废!
Cross Frame
早之前提到这个方法的是这篇文章http://softwareas.com/cross-domain-communication-with-iframes当然,阅读量非常之高,所以再HTML5出来了postMessage之后,作者又更新了这篇文章。
Cross Frame是FIM的一个变种,它借助了一个空白的iframe,不会产生多余的浏览器历史记录,也不需要轮询URL的改变,在可用性和性能上都做了很大的改观。它的基本原理大致是这样的,假设在域www.a.com上有页面A.html和一个空白代理页面proxyA.html, 另一个域www.b.com上有个页面B.html和一个空白代理页面proxyB.html,A.html需要向B.html中发送消息时,页面会创建一个隐藏的iframe, iframe的src指向proxyB.html并把message作为URL frag,由于B.html和proxyB.html是同域,所以在iframe加载完成之后,B.html可以获得iframe的URL,然后解析出message,并移除该iframe。当B.html需要向A.html发送消息时,原理一样。Cross Frame是很好的双向通信方式,而且安全高效,但是它在Opera中无法使用,不过在Opera下面我们可以使用更简单的window.postMessage来代替。
现在假设 A..html 被iframe的页面是B.html B.html要向A.html传递自己的真实高度,会临时内嵌一个proxyA.html,proxyA.html与A..html传话之后,删除。
A..html
<iframe id="b_iframe" height="0" width="0" src="http://www.sina.com.cn/b.html" border="none"></iframe>
B.html
var b_width = Math.max(document.documentElement.clientWidth,document.body.clientWidth); var b_height = Math.max(document.documentElement.clientHeight,document.body.clientHeight); var proxy = document.createElement('iframe'); proxy.style.display = 'none'; proxy.src = 'http://www.a.com/proxyA.html'+hash; document.body.appendChild(proxy); var c_iframe = document.getElementById("proxyA"); c_iframe.src = c_iframe.src+"#"+b_width+"&"+b_height; }
proxyA.html
var b_iframe = parent.parent.document.getElementById("b_iframe"); var hash_url = window.location.hash.split("#")[1].split("&"); var hash_width = hash_url[0]+"px"; var hash_height = hash_url[1]+"px"; b_iframe.style.width = hash_width; b_iframe.style.height = hash_height;
postMessage(HTML5)
html5中有个很酷的功能,就是跨文档消息传输(Cross Document Messaging)。新一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。
使用方法如下:
otherWindow.postMessage(message, targetOrigin);
说明:
otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性,window.open的返回值等。
message: 所要发送的数据,string类型。
targetOrigin: 用于限制otherWindow,“*”表示不作限制
www.a.com/A .html中的代码:
<iframe id="ifr" src="http://www.b.com/b.html"></iframe>
window.onload = function() { var ifr = document.getElementById('ifr'); // 若写成'http://www.c.com'就不会执行postMessage了 var targetOrigin = 'http://www.b.com'; ifr.contentWindow.postMessage('sayHello', targetOrigin); };
B.html中的代码
window.addEventListener('message', function(e){ // 通过origin属性判断消息来源地址 if (e.origin == 'http://www.a.com' && e.data=='sayHello') { alert('Hello World'); } }, false);
库支持现在可以了。以下所有提供的一个接口的postMessage它是可用的,但如果它不是,回落到本文中描述的原始的技术:
document.domain+iframe
对于相同主域,子域不同的页面交互信息,可以通过设置document.domain的办法来解决。
比如shenghuo.alipay.com/A.html和personal.alipay.com/B.html设置document.domain = ‘alipay.com’。
A.html
document.domain='alipay.com'; var ifr = document.createElement('iframe'); ifr.src = 'personal.alipay.co/B.htm'; ifr.style.display = 'none'; document.body.appendChild(ifr); ifr.onload = function(){ //获取iframe的document对象,W3C的标准方法是iframe.contentDocument, //IE6、7可以使用document.frames[ID].document //为了更好兼容,可先获取iframe的window对象iframe.contentWindow var doc = ifr.contentDocument || ifr.contentWindow.document; // 在这里操纵b.html alert(doc.getElementById("test").innerHTML); };
B.html
<!DOCTYPE thml> <html> <head> <title></title> <script type="text/javascript"> document.domain='alipay.com'; </script> </head> <body> <h1 id="test">Hello World</h1> </body> </html>
如果b.html要访问a.html,可在子窗口(iframe)中通过window.parent来访问父窗口的window对象,然后就可以为所欲为了(window对象都有了,还有啥不行的),同理子窗口也可以和子窗口之间通信。
于是,我们可以通过b.html的XMLHttpRequest来获取数据,再传给a.html,从而解决跨子域获取数据的问题。
动态script标签(Dynamic Script Tag)
这种方法也叫“动态脚本注入”。这种技术克服了XMLHttpRequest的最大限制,也就是跨域请求数据。直接用JavaScript创建一个新的脚本标签,然后设置它的src属性为不同域的URL。
www.a.com/a.html中的script
通过动态标签注入的必须是可执行的JavaScript代码,因此无论是你的数据格式是啥(xml、json等),都必须封装在一个回调函数中。一个回调函数如下:
www.a.com/a.html中的script
var dynScript = document.createElement('script'); dynScript.src = 'http://www.b.com/b.js'; dynScript.setAttribute("type", "text/javascript"); document.getElementsByTagName('head')[0].appendChild(dynScript); function dynCallback(data){ //处理数据, 此处简单示意一下 alert(data.content); }
在这个例子中,www.b.com/b.js需要将数据封装在上面这个dynCallback函数中,如下:
dynCallback({"content": "Hello World"})你可以传递更为复杂的数据。
不过动态脚本注入还是存在不少问题的,下面我们拿它和XMLHttpRequest来对比一下:
XmlHttpRequest | Dynamic Script Tag | |
---|---|---|
跨浏览器兼容 | No | Yes |
跨域限制 | Yes | No |
接收HTTP状态 | Yes | No (除了200) |
支持Get、Post | Yes | No (GET only) |
发送、接收HTTP头 | Yes | No |
接收XML | Yes | Yes |
接收JSON | Yes | Yes |
支持同步、异步 | Yes | No (只能异步) |
可以看出,动态脚本注入还是有不少限制,只能使用Get,不能像XHR一样判断Http状态等。
而且使用动态脚本注入的时候必须注意安全问题。因为JavaScript没有任何权限与访问控制的概念,通过动态脚本注入的代码可以完全控制整个页面,所以引入外部来源的代码必须多加小心。
PS:文章有错误之处还请同仁指正,发现错误才会提高!参考文献如果有遗漏的,欢迎邮我哦^^!
参考列表:
Two Methods for Handling Cross-Domain Ajax Calls
Using window.name as a local data cache in web browsers
CROSS-DOMAIN COMMUNICATION WITH IFRAMES