说到javascript跨越问题,首页必然需要引入一个概念。那就是为什么会存在跨域问题?又该如何解决呢?
一、同源策略(same-origin policy)
在web应用安全模型里同源策略是一个很重要的概念。它是由Netscape提出的一个著名的安全策略,现在所有的可支持javascript的浏览器都会使用这个策略。
同源策略,又称为单源策略,它限制了一个源(origin)中加载文本或脚本与来自其它源(origin)中资源的交互方式。
Mozilla认为两个页面它们拥有相同的协议、端口(如果指明)、主机名,那么这两个页面就拥有相同的源。
下面来看一个例子,假设有一个url地址为: http://www.a.com/page/a.html的同源检测示例:
URL | 非IE浏览器结果 | IE浏览器结果 | 原因 |
---|---|---|---|
http://www.a.com/page/b.html | 成功 | 成功 | - |
http://www.a.com/app/c.html | 成功 | 成功 | - |
http://username:password@www.a.com/page/b.html | 成功 | 成功 | - |
http://www.a.com:81/page/d.html | 失败 | 成功 | 端口不同 |
https://www.a.com/page/e.html | 失败 | 失败 | 协议不同 |
http://www.b.a.com/page/c.html | 失败 | 失败 | 主机不同 |
http://www.c.com/page/c.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 中的一个脚本执行了下列语句:
|
|
这条语句执行之后,页面将会成功地通过对 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设置为相同的值。必须要这么做,即使是简单的将父域设置为其原来的值。没有这么做的话可能导致授权错误。
举个例子
a.taobao.com嵌入一个b.taobao.com的页面,并且需要做高度自适应,那么可以这么来做:
- 在a页面中设置domain并定义autoHeight方法(用处为改变iframe的高度)
|
|
- 在b页面,页面加载之后或者所有涉及到高度变化的事件中,调用a页面的方法:
|
|
2、使用代理页
如果主域名也不同该怎么做呢?如:a.com/index.html和b.com/iframe.html
有一种办法是采用代理页的方式
即在b.com下建立一个b.com/proxy.html代理页面,还是以高度自适应为例来说明:
a页面iframe该proxy页面,并将高度以hash值的方式填到这个嵌入proxy.html的iframe的src中去
在proxy.html中去拿到这个hash中的高度值,并去操作父父层页面的高度
代码如下:
|
|
|
|
3、JSONP(创建script标签形式)
所谓JSONP的方式简单来说就是利用script标签不受同源策略的限制这一特性。
我们通过script标签的形式把想要请求的地址作为src传入,从而获得相应的文件或者JSON数据。
a.com下的数据fn({“name”: “amo”});
b.com下的函数function fn(res){ console.log(res.name); }
下面我们来看下具体来写应该怎样做:
- 在A.com网站下:
|
|
- 在B.com的jsonp.js通过拿到这个callback参数,并把它填充到具体的数据中,格式如下
|
|
这样就相当于页面上是这样的
|
|
当然这里只是一个思路的过程,实际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中引入如下代码
|
|
在parent.html加载完iframe之后引入如下代码
|
|
这种方法虽然很方便,但是是H5的特性,所以在使用前先考虑是否需要兼容低版本浏览器
5、服务器代理
6、flash方式
后两种方式,这里不做介绍。