JS跨域(ajax跨域、iframe跨域)解决方法及原理详解(jsonp)

这里说的js跨域是指经过js在不一样的域之间进行数据传输或通讯,好比用ajax向一个不一样的域请求数据,或者经过js获取页面中不一样域的框架中(iframe)的数据。只要协议、域名、端口有任何一个不一样,都被看成是不一样的域。php

下表给出了相对 http://store.company.com/dir/page.html 同源检测的结果:html

要解决跨域的问题,咱们可使用如下几种方法:html5

一、经过jsonp跨域【解决ajax跨域】jquery

在js中,咱们直接用XMLHttpRequest请求不一样域上的数据时,是不能够的。可是,在页面上引入不一样域上的js脚本文件倒是能够的,jsonp正是利用这个特性来实现的。ajax

好比,有个a.html页面,它里面的代码须要利用ajax获取一个不一样域上的json数据,假设这个json数据地址是http://example.com/data.php,那么a.html中的代码就能够这样:json

咱们看到获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,可是你用其余的也同样。固然若是获取数据的jsonp地址页面不是你本身能控制的,就得按照提供数据的那一方的规定格式来操做了。跨域

由于是当作一个js文件来引入的,因此http://example.com/data.php返回的必须是一个能执行的js文件,因此这个页面的php代码多是这样的:浏览器

最终那个页面输出的结果是:安全

因此经过http://example.com/data.php?callback=dosomething获得的js文件,就是咱们以前定义的dosomething函数,而且它的参数就是咱们须要的json数据,这样咱们就跨域得到了咱们须要的数据。服务器

这样jsonp的原理就很清楚了,经过script标签引入一个js文件,这个js文件载入成功后会执行咱们在url参数中指定的函数,而且会把咱们须要的json数据做为参数传入。因此jsonp是须要服务器端的页面进行相应的配合的。

知道jsonp跨域的原理后咱们就能够用js动态生成script标签来进行跨域操做了,而不用特地的手动的书写那些script标签。若是你的页面使用jquery,那么经过它封装的方法就能很方便的来进行jsonp操做了。

原理是同样的,只不过咱们不须要手动的插入script标签以及定义回掉函数。jquery会自动生成一个全局函数来替换callback=?中的问号,以后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的做用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

延伸阅读ajax跨域:Jsonp原理解析

 

二、经过修改document.domain来跨子域【解决iframe跨域】

浏览器都有一个同源策略,其限制之一就是第一种方法中咱们说的不能经过ajax的方法去请求不一样源中的文档。 它的第二个限制是浏览器中不一样域的框架之间是不能进行js的交互操做的。有一点须要说明,不一样的框架之间(父子或同辈),是可以获取到彼此的window对象的,但蛋疼的是你却不能使用获取到的window对象的属性和方法(html5中的postMessage方法是一个例外,还有些浏览器好比ie6也可使用top、parent等少数几个属性),总之,你能够当作是只能获取到一个几乎无用的window对象。好比,有一个页面,它的地址是http://www.example.com/a.html  , 在这个页面里面有一个iframe,它的src是http://example.com/b.html, 很显然,这个页面与它里面的iframe框架是不一样域的,因此咱们是没法经过在页面中书写js代码来获取iframe中的东西的:

这个时候,document.domain就能够派上用场了,咱们只要把http://www.example.com/a.html 和 http://example.com/b.html这两个页面的document.domain都设成相同的域名就能够了。但要注意的是,document.domain的设置是有限制的,咱们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。例如:a.b.example.com 中某个文档的document.domain 能够设成a.b.example.com、b.example.com 、example.com中的任意一个,可是不能够设成 c.a.b.example.com,由于这是当前域的子域,也不能够设成baidu.com,由于主域已经不相同了。

在页面 http://www.example.com/a.html 中设置document.domain:

在页面 http://example.com/b.html 中也设置document.domain,并且这也是必须的,虽然这个文档的domain就是example.com,可是仍是必须显示的设置document.domain的值:

这样咱们就能够经过js访问到iframe中的各类属性和对象了。

不过若是你想在http://www.example.com/a.html 页面中经过ajax直接请求http://example.com/b.html 页面,即便你设置了相同的document.domain也仍是不行的,因此修改document.domain的方法只适用于不一样子域的框架间的交互。若是你想经过ajax的方法去与不一样子域的页面交互,除了使用jsonp的方法外,还能够用一个隐藏的iframe来作一个代理。原理就是让这个iframe载入一个与你想要经过ajax获取数据的目标页面处在相同的域的页面,因此这个iframe中的页面是能够正常使用ajax去获取你要的数据的,而后就是经过咱们刚刚讲得修改document.domain的方法,让咱们能经过js彻底控制这个iframe,这样咱们就可让iframe去发送ajax请求,而后收到的数据咱们也能够得到了。

 

三、使用window.name来进行跨域【解决iframe跨域】

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的全部的页面都是共享一个window.name的,每一个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的全部页面中的,并不会因新页面的载入而进行重置。

好比:有一个页面a.html,它里面有这样的代码:

再看看b.html页面的代码:

a.html页面载入后3秒,跳转到了b.html页面,结果为:

咱们看到在b.html页面上成功获取到了它的上一个页面a.html给window.name设置的值。若是在以后全部载入的页面都没对window.name进行修改的话,那么全部这些页面获取到的window.name的值都是a.html页面设置的那个值。固然,若是有须要,其中的任何一个页面均可以对window.name的值进行修改。注意,window.name的值只能是字符串的形式,这个字符串的大小最大能容许2M左右甚至更大的一个容量,具体取决于不一样的浏览器,但通常是够用了。

上面的例子中,咱们用到的页面a.html和b.html是处于同一个域的,可是即便a.html与b.html处于不一样的域中,上述结论一样是适用的,这也正是利用window.name进行跨域的原理。

下面就来看一看具体是怎么样经过window.name来跨域获取数据的。仍是举例说明。

好比有一个www.example.com/a.html页面,须要经过a.html页面里的js来获取另外一个位于不一样域上的页面www.php-note.com/data.html里的数据。

data.html页面里的代码很简单,就是给当前的window.name设置一个a.html页面想要获得的数据值。data.html里的代码:

那么在a.html页面中,咱们怎么把data.html页面载入进来呢?显然咱们不能直接在a.html页面中经过改变window.location来载入data.html页面,由于咱们想要即便a.html页面不跳转也能获得data.html里的数据。答案就是在a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取data.html的数据,而后a.html再去获得iframe获取到的数据。

充当中间人的iframe想要获取到data.html的经过window.name设置的数据,只须要把这个iframe的src设为www.php-note.com/data.html就好了。而后a.html想要获得iframe所获取到的数据,也就是想要获得iframe的window.name的值,还必须把这个iframe的src设成跟a.html页面同一个域才行,否则根据前面讲的同源策略,a.html是不能访问到iframe里的window.name属性的。这就是整个跨域过程。

看下a.html页面的代码:

上面的代码只是最简单的原理演示代码,你能够对使用js封装上面的过程,好比动态的建立iframe,动态的注册各类事件等等,固然为了安全,获取完数据后,还能够销毁做为代理的iframe。网上也有不少相似的现成代码,有兴趣的能够去找一下。

经过window.name来进行跨域,就是这样子的。

 

四、使用HTML5中新引进的window.postMessage方法来跨域传送数据【解决iframe跨域】【能够不考虑此解决方案】

window.postMessage(message,targetOrigin)  方法是html5新引进的特性,可使用它来向其它的window对象发送消息,不管这个window对象是属于同源或不一样源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。

调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,若是不想限定域,可使用通配符 *  。

须要接收消息的window对象,但是经过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。

上面所说的向其余window对象发送消息,其实就是指一个页面有几个框架的那种状况,由于每个框架都有一个window对象。在讨论第二种方法的时候,咱们说过,不一样域的框架间是能够获取到对方的window对象的,并且也可使用window.postMessage这个方法。下面看一个简单的示例,有两个页面

 

 

咱们运行a页面后获得的结果:

咱们看到b页面成功的收到了消息。

使用postMessage来跨域传送数据仍是比较直观和方便的,可是缺点是IE六、IE七、IE8不支持,因此用不用还得根据实际须要来决定。

 

结语:

除了以上几种方法外,还有flash、在服务器上设置代理页面等跨域方式,这里就不作介绍了。

以上四种方法,能够根据项目的实际状况来进行选择应用,我的认为window.name的方法既不复杂,也能兼容到几乎全部浏览器,这真是极好的一种跨域方法。