前端遇到的跨域问题及解决方案二

解决不同域之间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它是可用的,但如果它不是,回落到本文中描述的原始的技术:

Porthole

XSSInterface

EasyXDM

jQuery PostMessage Plugin

 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来对比一下:

 XmlHttpRequestDynamic 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

Window.postMessage

使用 window.name 解决跨域问题

Using window.name as a local data cache in web browsers

谈谈我对 window.name 实现跨域的理解

JS之Window对象

WEB跨域问题及解决方案

CROSS-DOMAIN COMMUNICATION WITH IFRAMES

如何解决js跨域问题

如何解决Ajax跨域问题-1

解决Ajax跨域问题

 

posted @ 2014-02-03 17:00  易小亨  阅读(397)  评论(0编辑  收藏  举报