什么是跨域?当两个域具有相同的协议、相同的端口、相同的host时,那么我们就可以认为它们是相同的域。比如:http://www.example.com/a.html 和 http://www.example.com/b/c.html 就属于相同的域,数据访问可通过 ajax 解决。反之如果不符合上述三个条件中任何一个,我们称之为不同域。比如 http://www.example.com/a.html 和 http://example.com/b.html。由于javascript同源策略的限制,js 语言本身是不具备跨域访问能力的,但是很多时候业务需求,加之程序员们丰富的想象力,出现了各种解决跨域问题的方案。这里就对比较主流的跨域方案做一个简单的汇总。
Image Beacon
这种跨域方案非常简单,利用的是图片可以跨域读取的性质,代码如下:
var img = new Image();
img.src = "http://example.com/data?value=123";
在 js 里执行上述两行代码,就可以向 http://example.com/data 传送数据了,而不用担心是否和 src 里面的地址不同域。数据部分嵌入在URL中,即"value=123" 部分。
这种方案优点在于非常简单,不会带来很多代码的管理问题。缺点在于它只能单向的给服务发送数据,再者由于数据嵌入在 URL 中,而URL的字符长度是受浏览器限制的,所以数据大小也会有限制。
Image Beacon 对于第三方的统计网站非常有用。百度统计、CNZZ等均有用到。
JSONP
JSONP 跨域算是用的比较多的了,研究下QQ空间,就会发现里面很多用了JSONP技术。JSONP利用的是 script 标签跨域请求 js 文件的能力。实现方式如下:
1)首先建立一个动态 script 标签
var script = document.createElement('script');
script.src = 'http://example.com/data?callback=fn';
document.body.appendChild(script);
2)注意上面的 src 部分,最后面带有参数 callback=fn。fn 必须是一个全局函数,且要在在 script 标签 append 到 body 里面之前要声明好的。
function fn(data) {
// 对data 进行处理
}
3)步骤1完成之后,script 标签动态的生成在 body 标签内。浏览器解析到script标签,给服务器(http://example.com)发送请求。服务器接受到请求后,将需要传给客户端的数据以json格式包在 fn 函数中,以参数的形式传进去,如下:
fn({value: 123}); //服务端传给客户端的 js
4)浏览器接受到服务器端传来的 js 代码,执行,发现 fn 函数已经声明在全局中了(步骤2),于是数据成功的传到了 js 代码中。
这种方式好处在于可以在客户端和服务器端传输数据,且不受长度限制。缺点在于由于是第三方服务器传来的值,安全性不可控,所以在上述例子中,fn 函数在操作data之前,必须对data进行校验,以保证没有恶意代码注入。
iframe 通信
有主页面A,嵌入一个iframe B(name 属性设为 "B"),A和B之间的通信可以分两种情况,一种A和B同域,一种A和B不同域。
1)A和B同域
由于是同域,浏览器允许他们之间互相访问,访问方式如下:
A访问B:winB = window.frames["B"].window; //winB 指向B中的window对象
B访问A:winA = parent.window; //winA 指向A中的window对象
2)A和B不同域
不同域的时候,上述访问就要出错了。由于安全问题,浏览器限制了不同域之间 js 访问权限。既然 js 直接访问失败,有没有其他方式可以实现主页面和不同域的 iframe 之间的通信呢?这个时候就要发挥程序员的 hack 精神了,下面的内容为跨域的情况,分两种情况讨论。
a)主页面A传数据给iframe B
在A中设置B的src属性形式如下:
<iframe src="http://b.com/hello#data" name="B"></iframe>
data即是A要传给B的值,此时B中可以通过 location.hash 来获取data的值了。
利用URL的hash传值,好处在于当通过js修改hash值时(URL改变),不会导致iframe 整个刷新。这样的话,如果需要A持续的给B传值,就可以不断的修改URL后面data的值了,而B可以通过onhashchange监听hash值的改变。低端浏览器由于不支持onhashchange,可以通过设置setInterval 来监听URL是否有改动,从而获得改动后的hash值。
b)iframe B 给主页面A传数据
在 iframe B 中再嵌入隐藏的一个 iframe C,设置iframe C 和主页面A同域但不同URL。假设A的url为 http://a.com,那么iframe C设置如下:
<iframe src="http://a.com/iframe_c#data" name="C"></iframe>
同样,URL中的hash部分为iframe B需要传给A的值。由于A和C之间是同域关系,是可以通过js直接访问的。因此只需要在C中通过JS获取到C的URL中data部分,然后传递给A就行了,实现代码如下:
parent.parent.fn(location.hash);
注意在执行上面这行代码之前必须在A中声明好全局函数fn,parent.parent 指向的即主页面A中的window对象。
主页面与iframe 之间的跨域通信还可以利用 window.name 这个属性的特殊性来达到。具体就不做说明了,有兴趣的同学可以在网上搜搜。跨域的方式很多,关键是选择一种即能满足本身的业务需求又不影响到代码的可维护性和扩展性。这篇文章算是一个总结,抛砖引玉。前端是个大坑,进去容易,要想明明白白的出来,还得费一番功夫。