「JavaScript」JS四种跨域方式详解

64

超详细并且带 Demo 的 JavaScript 跨域指南来了!

本文基于你了解 JavaScript 的同源策略,并且了解使用跨域跨域的理由。

1. JSONP

首先要介绍的跨域方法必然是 JSONP。

现在你想要获取其他网站上的 JavaScript 脚本,你非常高兴的使用 XMLHttpRequest 对象来获取。但是浏览器一点儿也不配合你,无情的弹出了下面的错误信息:

XMLHttpRequest cannot load http://x.com/main.dat. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://y.com' is therefore not allowed access.

你心里肯定会想,我难道要用后台做个爬虫来获取这个数据吗?!(;°○° )
为了避免这种蛋疼的事情发生,JSONP 就派上用场了。

<script> 标签是不受同源策略的限制的,它可以载入任意地方的 JavaScript 文件,而并不要求同源。

所以 JSONP 的理念就是,我和服务端约定好一个函数名,当我请求文件的时候,服务端返回一段 JavaScript。这段 JavaScript 调用了我们约定好的函数,并且将数据当做参数传入。
非常巧合的一点(其实并不是),JSON 的数据格式和 JavaScript 语言里对象的格式正好相同。所以在我们约定的函数里面可以直接使用这个对象。

光说不练假把式,让我们来看一个例子:

你需要获取数据的页面 index.html:

<script>
    function getWeather(data) {
        console.log(data);
    }
</script>

<script src="http://x.y.com/xx.js">

http://x.y.com/xx.js 文件内容:

getWeather({
    "城市": "北京",
    "天气": "大雾"
});

我们可以看到,在我们定义了 getWeather(data) 这个函数后,直接载入了 xx.js。
在这个脚本中,执行了 getWeather 函数,并传入了一个对象。然后我们在这个函数中将这个对象输出到 console 中。

这就是整个 JSONP 的流程。

2. document.domain

使用条件:

  1. 有其他页面 window 对象的引用。
  2. 二级域名相同。
  3. 协议相同。
  4. 端口相同。

document.domain 默认的值是整个域名,所以即使两个域名的二级域名一样,那么他们的 document.domain 也不一样。

使用方法就是将符合上述条件页面的 document.domain 设置为同样的二级域名。这样我们就可以使用其他页面的 window 对象引用做我们想做的任何事情了。(╯▔▽▔)╯

补充知识:

  • x.one.example.com 和 y.one.example.com 可以将 document.domain 设置为 one.example.com,也可以设置为 example.com。
  • document.domain 只能设置为当前域名的一个后缀,并且包括二级域名或以上(.edu.cn 这种整个算顶级域名)。

我们直接操刀演示,用两个网站 http://wenku.baidu.com/http://zhidao.baidu.com/
这两个网站都是 http 协议,端口都是 80, 且二级域名都是 baidu.com。

打开 http://wenku.baidu.com/,在 console 中输入代码:

document.domain = 'baidu.com';

var otherWindow = window.open('http://zhidao.baidu.com/');

我们现在已经发现百度知道的网页已经打开了,在百度知道网页的 console 中输入以下代码:

document.domain = 'baidu.com';

现在回到百度文库的网页,我们就可以使用百度知道网页的 window 对象来操作百度知道的网页了。例如:

var divs = otherWindow.document.getElementsByTagName('div');

上面这个例子的使用方法并不常见,但是非常详细的说明了这种方法的原理。
这种方法主要用在控制 <iframe> 的情况中。

比如我的页面(http://one.example.com/index.... <iframe>

<iframe id="iframe" src="http://two.example.com/iframe.html"></iframe>

我们在 iframe.html 中使用 JavaScript 将 document.domain 设置好,也就是 example.com。

在 index.html 执行以下脚本:

var iframe = document.getElementById('iframe');

document.domain = 'example.com';

iframe.contentDocument; // 框架的 document 对象
iframe.contentWindow; // 框架的 window 对象

这样,我们就可以获得对框架的完全控制权了。

补充知识(绝对干货):
当两个页面不做任何处理,但是使用了框架或者 window.open() 得到了某个页面的 window 对象的引用,我们可以直接访问的属性有哪些?

方法
window.blur
window.close
window.focus
window.postMessage
window.location.replace
属性 权限
window.closed 只读
window.frames 只读
window.length 只读
window.location.href 只写
window.opener 只读
window.parent 只读
window.self 只读
window.top 只读
window.window 只读

3. window.name

我们来看以下一个场景:

随意打开一个页面,输入以下代码:

window.name = "My window's name";
location.href = "http://www.qq.com/";

再检测 window.name :

window.name; // My window's name

可以看到,如果在一个标签里面跳转网页的话,我们的 window.name 是不会改变的。
基于这个思想,我们可以在某个页面设置好 window.name 的值,然后跳转到另外一个页面。在这个页面中就可以获取到我们刚刚设置的 window.name 了。

由于安全原因,浏览器始终会保持 window.namestring 类型。

这个方法也可以应用到与 <iframe> 的交互上来。

我的页面(http://one.example.com/index.... <iframe>

<iframe id="iframe" src="http://omg.com/iframe.html"></iframe>

在 iframe.html 中设置好了 window.name 为我们要传递的字符串。
我们在 index.html 中写了下面的代码:

var iframe = document.getElementById('iframe');
var data = '';

iframe.onload = function() {
    data = iframe.contentWindow.name;
};

定睛一看,为毛线报错?
细心的读者们肯定已经发现了,两个页面完全不同源啊!
由于 window.name 不随着 URL 的跳转而改变,所以我们使用一个暗黑技术来解决这个问题:

var iframe = document.getElementById('iframe');
var data = '';

iframe.onload = function() {
    iframe.onload = function(){
        data = iframe.contentWindow.name;
    }
    iframe.src = 'about:blank';
};

或者将里面的 about:blank 替换成某个同源页面(最好是空页面,减少加载时间)。

补充知识:
about:blankjavascript:data: 中的内容,继承了载入他们的页面的源。

这种方法与 document.domain 方法相比,放宽了域名后缀要相同的限制,可以从任意页面获取 string 类型的数据。

4. [HTML5] postMessage

在 HTML5 中, window 对象增加了一个非常有用的方法:

windowObj.postMessage(message, targetOrigin);
  • windowObj: 接受消息的 Window 对象。
  • message: 在最新的浏览器中可以是对象。
  • targetOrigin: 目标的源,* 表示任意。

这个方法非常强大,无视协议,端口,域名的不同。下面是烤熟的栗子:

var windowObj = window; // 可以是其他的 Window 对象的引用
var data = null;

addEventListener('message', function(e){
    if(e.origin == 'http://jasonkid.github.io/fezone') {
        data = e.data;
        
        e.source.postMessage('Got it!', '*');
    }
});

message 事件就是用来接收 postMessage 发送过来的请求的。函数参数的属性有以下几个:

  • origin: 发送消息的 window 的源。
  • data: 数据。
  • source: 发送消息的 Window 对象。

Demo

https://github.com/JasonKid/f...

两种服务端相关跨域方法

「JavaScript」两种服务端相关跨域方法详解 ← 反向代理、CORS方法请点这里

觉得不错的话按顶部的推荐,让更多人看到吧~ㄟ(▔▽▔ㄟ)


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

18 条评论
meikidd · 2015年09月01日

就冲着楼主的id,也一定要来顶一个。

  1. domain方案,一级域名不同的话,就没办法了

  2. window.name需要将iframe刷新为'about:blank;',很多场景下不适用

  3. postMessage是发送到所有窗口的

  4. 另外一个楼主没有提到的,且被广泛使用的方案是 CORS

+1 回复

JasonKidd 作者 · 2015年09月01日

感谢~

+1 回复

Dont · 2015年09月01日

细心的读者发现:

message 时间就是用来接收 postMessage 发送过来的请求的。函数参数的属性有以下几个

时间->事件 :)

回复

哈拉哈拉 · 2015年09月01日

没有做过有这方面需求的项目,就jsonp和html5的看的比较明白。

回复

JasonKidd 作者 · 2015年09月02日

ID 确实。。不过你的问题回应如下:
1、文章中有写,必须将document.domain设置为一样,如果一级域名不一样的话,document.domain又如何能够设置成一样呢?
2、小弟才浅,请赐教哪些场景不适用。
3、并不是,是发送到调用这个方法的Window对象,参见 https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
4、这个方法不算纯JAVASCRIPT,你可以参见我的另外一篇文章 http://segmentfault.com/a/1190000003693381

回复

meikidd · 2015年09月02日

我其实是想补充一下这些方案各自的受限条件,很赞的文章。
2.如果 iframe 只是单纯用来发送跨域请求的话问题不大。但如果 iframe 内包含了 ui 界面和用户交互,这时候将 iframe.src = 'about:blank' 就会导致 iframe 刷新
3.是我记错了
4.抱歉之前没看到你的第二篇。其实不必明确划分前后端,很多方案都是需要前后端一起统筹来看的,比如 jsonp 也是

回复

JasonKidd 作者 · 2015年09月02日

关于第二点,无论将iframe.src重新设为什么值,都会导致iframe刷新吧。

回复

icewind · 2015年09月02日

我看过的写的最好的跨域文章了,点个赞

回复

执剑为何 · 2015年09月05日

关于你的第三种方法,我的代码如下:

    var iframe = document.getElementById('iframe');
    var data = '';

    iframe.onload = function() {
        iframe.onload = function(){
        data = iframe.contentDocument;
        console.log(data);
          }
      iframe.src = 'about:blank';
};

我的iframe如下:

<iframe id="iframe" src="http://www.qq.com/"></iframe>

可是我的控制台得到的HTML文档完全是空文档啊,能详细说下这个是怎么获取数据的吗?

回复

执剑为何 · 2015年09月05日

至于第四种方法,因为h5不太懂,所以没看懂到底在干嘛

回复

JasonKidd 作者 · 2015年09月05日

1.你自己看文章,iframe中的页面需要设置window.name来进行数据传递,你又不能控制QQ的页面。
2.就是空文档,为了iframe和父页面同源。

回复

执剑为何 · 2015年09月05日

哦哦。懂了,还是要借助window.name的原来

回复

执剑为何 · 2015年09月05日

还以为通过iframe的src可以获取整个不在同一域下的那个页面的整个文档呢

回复

JasonKidd 作者 · 2015年09月05日

可以通过服务器的 反向代理,在最后那两个服务端相关跨域里。

回复

灯盏细辛 · 2015年09月09日

特来顶id的,三双王,我的偶像

回复

leopard7777777 · 2015年09月11日

iframe监控hash change事件可以单向传播数据。iframe里面再引用外层页面,做一个iframe中的iframe即可调用引用页的javascript

回复

马铃薯和土豆 · 2015年09月18日

看得我云里雾里。。。

回复

JasonKidd 作者 · 2015年10月14日

哈哈

回复

江伟 · 2017年04月11日

为什么没有cors

回复

载入中...