什么是跨域?
现代浏览器出于安全考虑,都会去遵守一个叫做“同源策略”(同源策略就是用来限制从一个源加载的文档或脚本与来自另一个源的资源进行交互)的约定,同源的意思是两个地址的协议、域名、端口号都相同的情况下,才叫同源。这个时候两个地址才可以相互访问 cookie、localStorage、sessionStorage、发送 ajax 请求,如果三者有一个不同,就是不同源,这时再去访问这些资源就叫做跨域。
备注:
1、端口和协议的不同,只能通过后台来解决
2、localhost和127.0.0.1虽然都指向本机,但也属于跨域;
但请求不会携带 cookie时,也就没有跨域限制,如下:
- js、css、image 等静态文件
- form 表单提交
同源策略限制了一下行为:
Cookie、LocalStorage 和 IndexDB 无法读取
DOM 和 JS 对象无法获取
Ajax请求发送不出去
如何解决跨域问题?
1.JSONP方法;
2.window.name方法;
3.document.domain方法;
4.window.postMessage方法;
JSONP方法
什么是Jsonp? Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
JSONP的原理其实就是利用引入script
不限制源的特点,把处理函数名作为参数传入,然后返回执行语句。
在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
比如,有个a.html页面,它里面的代码需要利用ajax获取一个不同域上的json数据,假设这个json数据地址是http://aaa.com/data.php,那么a.html中的代码就可以这样:
<!-- a.html --> <script> function dealData (data) { console.log(data); } </script> <script src='http://aaa.com/data.php?callback=dealData'></script>
我们看到获取数据的地址后面还有一个callback参数,按惯例是用这个参数名,但是你用其他的也一样。当然如果获取数据的jsonp地址页面不是你自己能控制的,就得按照提供数据的那一方的规定格式来操作了。
因为是当做一个js文件来引入的,所以http://aaa.com/data.php返回的必须是一个能执行的js文件,所以这个页面的php代码可能是这样的:
<?php $callback = $_GET['callback'];//得到回调函数名; $data = 'data';//要返回的数据; echo $callback.'('.json_encode($data).')';//输出。 ?>
如果在jQuery中用JSONP的话就更加简单了:
<script> $.getJSON(''http://aaa.com/data.php?callback=?', function (data) { console.log(data); }); </script>
注意jQuery会自动生成一个全局函数来替换callback=?
中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON
方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用JSONP的回调函数。
window.name方法
关键点:window.name在页面的生命周期里共享一个window.name;
兼容性:所有浏览器都支持;
优点:
最简单的利用了浏览器的特性来做到不同域之间的数据传递;
不需要前端和后端的特殊配制;
缺点:
大小限制:window.name最大size是2M左右,不同浏览器中会有不同约定;
安全性:当前页面所有window都可以修改,很不安全;
数据类型:传递数据只能限于字符串。
设想场景:在一个页面 aaa.com/3a.html
中,我们想获取 bbb.com/3b.html
中的数据,以下是解决方案:
<!-- 3b.html --> <script> window.name = '3a.html想要的3b.html里面的数据'; //这是就是我们需要通信的数据 </script>
<!-- 3a.html --> <html> <head> <script> function getData () { var iframe = document.getElementById('iframe'); iframe.src = 'bbb.com/3b.html'; // 这里让iframe与父页面同源 iframe.onload = function () { var data = iframe.contentWindow.name; //在这里我们得到了跨域页面中传来的数据 }; } </script> </head> <body>
<iframe id="iframe" src = 'bbb.com/3b.html' onload()="getData ()">
</iframe>
</body> </html>
在3a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取3b.html的数据,然后a.html再去得到iframe获取到的数据。充当中间人的iframe想要获取到3b.html的通过window.name设置的数据,只需要把这个iframe的src设为bbb.com/3b.html就行了。然后3a.html想要得到iframe所获取到的数据,也就是想要得到iframe的window.name的值,还必须把这个iframe的src设成跟3a.html页面同一个域才行,不然根据前面讲的同源策略,3a.html是不能访问到iframe里的window.name属性的。这就是整个跨域过程。
document.domain方法
设想场景:有一个页面 http://www.example.com/a.html
,它里面有一个iframe
,这个iframe
的源是 http://example.com/b.html
,很显然它们是不同源的,所以我们无法在父页面中操控子页面的内容。
解决方案:
<!-- b.html --> <script> document.domain = 'example.com'; </script>
<!-- a.html --> <script> document.domain = 'example.com'; var iframe = document.getElementById('iframe').contentWindow.document; //后面就可以操作iframe里的内容了... </script>
window.postMessage方法
window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。
需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。
上面所说的向其他window对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。在讨论第二种方法的时候,我们说过,不同域的框架间是可以获取到对方的window对象的,而且也可以使用window.postMessage这个方法。下面看一个简单的示例,有两个页面
<!-- http://test.com/a.html --> <script> function onLoad(){ var iframe=document.getElementById('iframe'); var win =iframe.contentWindow; win.postMessage('this is message from a.html','*'); } </script> <iframe id="iframe" src="http://www.test.com/b.html" onload="onLoad()"></iframe>
<!-- http://test.com/b.html --> <script> window.onmessage=function(e){ //注册message事件用来接收消息 e =e||event;//获取事件对象 alert(e.data); } </script>
使用postMessage来跨域传送数据还是比较直观和方便的,但是缺点是IE6、IE7不支持,所以用不用还得根据实际需要来决定。