本文深入解析JavaScript同源策略导致的跨域限制,详细对比CORS、JSONP及document.domain三种主流解决方案的实现原理与适用场景。重点分析CORS的现代浏览器支持与全请求类型优势,JSONP的兼容性与GET限制,以及子域共享下的domain设置技巧,帮助开发者根据实际需求选择最佳跨域策略。
跨域概念与同源策略限制
什么是跨域?
核心概念:只要协议、域名、端口中任意一项存在差异,浏览器即判定为不同的域,从而触发同源策略限制。
代码如下:
URL 说明 是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js 同一域名下 允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js 同一域名下不同文件夹 允许
http://www.a.com:8000/a.js
http://www.a.com/b.js 同一域名,不同端口 不允许
http://www.a.com/a.js
https://www.a.com/b.js 同一域名,不同协议 不允许
http://www.a.com/a.js
http://70.32.92.74/b.js 域名和域名对应ip 不允许
http://www.a.com/a.js
http://script.a.com/b.js 主域相同,子域不同 不允许
http://www.a.com/a.js
http://a.com/b.js 同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js 不同域名 不允许
对于端口和协议的不同,通常只能通过后端配置来解决。
CORS跨域资源共享方案
跨域资源共享(CORS)
CORS(Cross-Origin Resource Sharing)即跨域资源共享,规范了浏览器与服务器在访问跨域资源时的交互方式。其核心机制是通过自定义的HTTP头部信息,让服务器告知浏览器是否允许该跨域请求成功或失败。
代码如下:
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("GET", "/trigkit4",true);
xhr.send();
</script>
上述代码中trigkit4为相对路径。若需使用CORS进行跨域访问,相关Ajax代码示例如下:
代码如下:
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://segmentfault.com/u/trigkit4/",true);
xhr.send();
</script>
代码的关键区别在于将相对路径替换为其他域的绝对路径,即目标跨域接口的地址。
服务器端对CORS的支持主要体现在设置Access-Control-Allow-Origin响应头。若浏览器检测到该头部包含允许的来源,则允许Ajax跨域访问。
解决跨域问题,主要可采用以下几种方法:
JSONP跨域实现原理与对比
通过jsonp跨域
首先需要明确JSONP的概念。根据维基百科定义,JSONP(JSON with Padding)是JSON格式的一种使用模式,旨在允许网页从不同域获取数据。
JSONP也被称为填充式JSON,它是JSON的一种应用变体,即将JSON数据包裹在函数调用中。例如:
代码如下:
callback({"name","trigkit4"});
JSONP由两部分组成:回调函数名和数据。回调函数是响应返回时页面中执行的函数,而数据则是传入该回调函数的JSON内容。
在JavaScript中,直接使用XMLHttpRequest请求不同域的数据是不被允许的。然而,在页面中引入不同域的JS脚本文件却是可行的,JSONP正是利用这一特性来实现跨域数据获取。例如:
代码如下:
<script type="text/javascript">
function dosomething(jsondata){
//处理获得的json数据
}
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>
当JS文件加载成功后,会执行URL参数中指定的函数,并将所需的JSON数据作为参数传入。因此,JSONP需要服务器端页面进行相应配合。
代码如下:
<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>
最终,输出结果为:dosomething(['a','b','c']);
若页面使用jQuery,可通过其封装的方法便捷地进行JSONP操作。
代码如下:
<script type="text/javascript">
$.getJSON('http://example.com/data.php?callback=?,function(jsondata)'){
//处理获得的json数据
});
</script>
jQuery会自动生成一个全局函数来替换callback=?中的问号,在获取数据后自动销毁该函数,实际上起到了临时代理函数的作用。$.getJSON方法会自动判断是否跨域:若不跨域,则调用普通Ajax方法;若跨域,则以异步加载JS文件的形式调用JSONP回调函数。
JSONP的优缺点
JSONP的优点在于:它不受XMLHttpRequest对象实现的Ajax请求所受到的同源策略限制;兼容性更好,在更古老的浏览器中均可运行,无需XMLHttpRequest或ActiveX支持;请求结束后可通过调用callback方式回传结果。
JSONP的缺点则是:仅支持GET请求,不支持POST等其他类型的HTTP请求;仅支持跨域HTTP请求这一种场景,无法解决不同域的两个页面之间如何进行JavaScript调用的问题。
CROS和JSONP对比
与JSONP相比,CORS更为先进、方便和可靠。
1、JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2、使用CORS,开发者可使用普通的XMLHttpRequest发起请求和获得数据,相比JSONP具有更好的错误处理机制。
document.domain跨子域方案
3、JSONP主要被老版本浏览器支持,它们往往不支持CORS;而绝大多数现代浏览器均已支持CORS。
浏览器实施同源策略,其限制包括:第一,禁止通过Ajax请求不同源的文档;第二,不同域的框架间禁止JS交互。尽管不同框架可获取window对象,但无法访问其属性和方法。例如,页面http://www.example.com/a.html包含src为http://example.com/b.html的iframe,因域名不同,页面JS无法获取iframe内容。
浏览器存在同源策略,其限制之一即前述方法中提到的不能通过Ajax请求不同源文档。其另一限制是浏览器中不同域的框架间不能进行JS交互。
代码如下:
function test(){
var iframe = document.getElementById('ifame');
var win = document.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
var doc = win.document;//这里获取不到iframe里的document对象
var name = win.name;//这里同样获取不到window对象的name属性
}
</script>
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
此时,document.domain即可发挥作用。只要将http://www.example.com/a.html和http://example.com/b.html这两个页面的document.domain设置为相同的域名即可。但需注意,document.domain的设置有限制:只能设置为自身或更高一级的父域,且主域必须相同。
1.在页面 http://www.example.com/a.html 中设置document.domain:
代码如下:
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
document.domain = 'example.com';//设置成主域
function test(){
alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
}
</script>2.在页面 http://example.com/b.html 中也设置document.domain:
代码如下:
<script type="text/javascript">
document.domain = 'example.com';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>
修改document.domain的方法仅适用于不同子域框架间的交互。