说到javascript跨越问题,首页必然需要引入一个概念。那就是为什么会存在跨域问题?又该如何解决呢?
一、同源策略(same-origin policy)
在web应用安全模型里同源策略是一个很重要的概念。它是由Netscape提出的一个著名的安全策略,现在所有的可支持javascript的浏览器都会使用这个策略。
同源策略,又称为单源策略,它限制了一个源(origin)中加载文本或脚本与来自其它源(origin)中资源的交互方式。
Mozilla认为两个页面它们拥有相同的协议、端口(如果指明)、主机名,那么这两个页面就拥有相同的源。
下面来看一个例子,假设有一个url地址为: http://www.a.com/page/a.html的同源检测示例:
从上表不难看出,对于IE来说在处理同源策略上有一些不同:
1、端口:IE未将端口号加入到同源策略的组成部分之中,因此 http://a.com:81/index.html 和http://a.com/index.html 属于同源并且不受任何限制。
2、授信范围(Trust Zones):两个相互之间高度互信的域名,如公司域名(corporate domains),不遵守同源策略的限制。
除这些指定的URL之外,也常常会有来自about:blank,javascript:和data:URLs中的内容,它们遵循源继承的原则,继承将其载入的文档所指定的源,因为它们的URL本身未指定任何关于自身源的信息。
二、Javascript跨域解决方案
1、设置Domain
页面可以改变本身的源,但会受到一些限制。脚本可以设置document.domain 的值为当前域的一个后缀
在同源策略中有一个例外,脚本可以设置 document.domain 的值为当前域的一个后缀,如果这样做的话,短的域将作为后续同源检测的依据。例如,假设在 http://b.a.com/page/a.html 中的一个脚本执行了下列语句:
1
document.domain = "a.com"
这条语句执行之后,页面将会成功地通过对 http://a.com/page/a.html 的同源检测。而同理,a.com 不能设置 document.domain 为 b.com.
浏览器单独保存端口号。任何的赋值操作,包括document.domain = documen.domain都会以null值覆盖掉原来的端口号。因此a.com:8080页面的脚本不能仅通过设置document.domain = “a.com”就能与company.com通信。赋值时必须带上端口号,以确保端口号不会为null。
注意:使用document.domain来安全是让子域访问其父域,需要同时将子域和父域的document.domain设置为相同的值。必须要这么做,即使是简单的将父域设置为其原来的值。没有这么做的话可能导致授权错误。
1
document.domain = "taobao.com"
- 在b页面,页面加载之后或者所有涉及到高度变化的事件中,调用a页面的方法:
1
2
3
4
5
function fixHeight (){
document.domain = 'taobao.com' ;
var oHeight = document.documentElement.scrollHeight;
window.parent.autoHeight(oHeight); //调用a页面的autoHeight方法,在该方法中去改变iframe的高度
}
2、使用代理页
如果主域名也不同该怎么做呢?如:a.com/index.html和b.com/iframe.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(function(win, doc){
win.onload = function () {
var url = "http://a.com/proxy.html" ;
var ifmProxy = doc.getElementById("ifmProxy" ); //iframe target
var hs; //height
var hsLastTime = 0 ; //height last time
var startTime = 0 ; //count
var loadIframeStart = win.setInterval( function () {
set Height();
}, 500 );
function setHeight () {
startTime++;
//get current height
hs = doc.documentElement.scrollHeight;
//update the hs height
if (hs !== hsLastTime) {
ifmProxy.src = url + "#" + hs;
hsLastTime = hs;
}
//clearCount and restart
if (startTime >= 500 ) {
clearInterval(loadIframeStart);
startTime = 0 ;
loadIframeStart = win.setInterval( function () {
set Height();
}, 500 )
}
}
};
})(window, document);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script>
try{
var ifmAnalyst = parent.parent.document.getElementById('isvIframeCon' ); //目标iframe
var hs; //获取的高度
var hsLastTime = 0 ; //上一次获取到的高度
var startTime = 0 ; //计时器初始值
var loadIframeStart = window.setInterval( function () {
updateHeight();
}, 500 );
function updateHeight () {
startTime++;
//获取当前的高度值
hs = window.location.hash;
//当高度改变则去更新iframe的高度同时将hsLastTime更新
if (hs !== hsLastTime) {
ifmAnalyst.style.height = hs.split("#" )[1 ] + "px" ;
hsLastTime = hs;
}
//当达到一定的次数,清理一次计时器,并重新开始监听
if (startTime >= 500 ) {
clearInterval(loadIframeStart);
startTime = 0 ;
loadIframeStart = window.setInterval( function () {
updateHeight();
}, 500 )
}
}
} catch(ex) {
}
</script>
3、JSONP(创建script标签形式)
所谓JSONP的方式简单来说就是利用script标签不受同源策略的限制这一特性。
我们通过script标签的形式把想要请求的地址作为src传入,从而获得相应的文件或者JSON数据。
a.com下的数据fn({“name”: “amo”});
b.com下的函数function fn(res){ console.log(res.name); }
下面我们来看下具体来写应该怎样做:
1
2
3
4
5
6
7
8
9
10
function createJs(sUrl){
var oScript = document.createElement('script' );
oScript.type = 'text/javascript' ;
oScript.src = sUrl;
document.getElementByTagName('head' )[0 ].appendChild(oScript);
}
createJs('jsonp.js?callback=fn' );
function fn(rs) {
console.log(rs);
}
在B.com的jsonp.js通过拿到这个callback参数,并把它填充到具体的数据中,格式如下
这样就相当于页面上是这样的
1
2
3
<script>
fn({"name" : "amo" });//fn这个函数的调用
</script>
当然这里只是一个思路的过程,实际JSONP的过程要比这复杂的多,比如说是否onload、回调状态码、格式的校验等。
4、postMessage方式
在HTML5中,提出了工作线程的概念(web workers)。Web Workers 允许开发人员编写能够长时间运行而不被用户所中断的后台程序,去执行事务或者逻辑,并同时保证页面对用户的及时响应。web worker一旦被创建,就可以通过postMessage 向任务池发送任务请求,执行完之后再通过 postMessage 返回消息给创建者指定的事件处理程序 ( 通过 onmessage 进行捕获 )。Web Workers 进程能够在不影响用户界面的情况下处理任务,并且,它还可以使用 XMLHttpRequest 来处理 I/O,但通常,后台进程(包括 Web Workers 进程)不能对 DOM 进行操作。如果希望后台程序处理的结果能够改变 DOM,只能通过返回消息给创建者的回调函数进行处理。
那么利用这个特性,我们怎么来实现跨域之间的通信呢?还是以高度自适应为例。
a.com/parent.html 嵌入 b.com/child.html该如何去获取到b下面的页面高度呢?
在child.html中引入如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(function(win, doc){
win.onload = function () {
var hs; //height
var hsLastTime = 0 ; //height last time
var startTime = 0 ; //count
var loadIframeStart = win.setInterval( function () {
set Height();
}, 500 );
function setHeight () {
startTime++;
//get current height
hs = doc.documentElement.offsetHeight;
//update the hs height
if (hs !== hsLastTime) {
window.parent.postMessage({h: hs}, '*' ); //指定是向父容器postmessage
hsLastTime = hs;
}
//clearCount and restart
if (startTime >= 500 ) {
clearInterval(loadIframeStart);
startTime = 0 ;
loadIframeStart = win.setInterval( function () {
set Height();
}, 500 )
}
}
};
})(window, document);
在parent.html加载完iframe之后引入如下代码
1
2
3
4
5
6
window.addEventListener("message" , function(data) {
var ifmAnalyst = document.getElementById('isvIframeCon' ),//目标iframe
ifmHeight = data.data.h;
//data.data.h即为高度
ifmAnalyst.style.height = ifmHeight + "px" ;
});
这种方法虽然很方便,但是是H5的特性,所以在使用前先考虑是否需要兼容低版本浏览器
5、服务器代理
6、flash方式
后两种方式,这里不做介绍。