调整字号

JavaScript 跨域通信的几种方法

由于同源策略(Same Origin Policy)的限制,JavaScript 中两个不同域的页面是无法互相访问数据的,这是出于安全的考虑,所谓同源,是指同一协议、同一域名、同一端口,只有这三者都相同,两个页面才可以互相访问各自的数据。协议是指 httphttpsftp 等,端口平常都是省略的,在 http 中默认均为 80 端口,https443 端口。

注意“同一域名”,是指主域和子域都必须相同。例如 www.a.com 的页面中嵌入了一个 one.a.com 的 iframe,那么就受到同源策略限制。a.com 和 one.a.com 也不视为同一域名。这一类情况比较容易解决,方法就是修改 document.domain

设置 document.domain

只要两个页面的 document.domain 相同,那么 JavaScript 就可以互相访问数据。document.domain 也不是可以随意修改的,只能在同一个主域内,例如 one.a.com 可以设置自己的 document.domaina.com,不能改为 b.comtwo.a.com。并且一旦改为 a.com,就无法再设置成 one.a.com。所以只要在两个页面中都加入下面语句,就可以用 JavaScript 互相访问了:

document.domain = "a.com";

如果是不同主域的两个页面,可以使用的方法有:

JSONP

JSONP 不需要 XHR,原理其实就是创建 script 元素,因为 script 有一个很爽的地方就是不受同源策略的限制,脚本的 src 可以来自任何域名。JSONP 与一般 JSON 请求的区别是,要在 URL 添加一个参数 callback=foo,后面跟上回调函数名,回调函数用来处理响应数据。被请求页面返回的内容形如 foo({ ... }),里面包裹的是 JSON 数据。jQuery 中有一个很方便的函数 jQuery.getJSON

window.postMessage

window.postMessage 是一个 HTML5 特性,可以向其他窗口或 frame/iframe 发送数据。这里的窗口是指通过 window.open 打开的窗口。使用方法:

otherWindow.postMessage(message, targetOrigin);

参数 message 是要发送的数据,不限于字符串,也可以发送对象等。targetOrigin 是一个字符串,表示接收方所在的域,包含协议和域名,例如 http://www.a.com。只有符合 targetOrigin 的窗口才会收到消息,如果要向所有窗口/框架发送,可以设为 *。注意这里的 otherWindow 是接收方的 window 对象。

在发送消息之前,接收方还要绑定 message 事件。message 事件触发时,事件的 event 对象有三个特别的属性:

  • data:接收到的数据
  • origin:发送方的域名
  • source:发送方的 window 对象

示例:

// 接收方先绑定 message 事件
window.addEventListener("message", function(e) {
  console.log("Just got a message from:", e.origin);
  console.log(e.data);  // 接收到的数据
})

// 数据发送方的代码
var win = window.open("http://one.a.com");  // 先取得对接收方 window 对象的引用,可以是 window.open 的返回值,或 iframe.contentWindow
win.postMessage("Hello Jack!", "http://one.a.com");

window.postMessage 在现代浏览器中都得到了实现,但是 IE6-7 并不支持,需要用其他方法。另外,IE8-9 中只支持传送字符串,如果要传送对象则要先使用 JSON.stringify,数据到达后再使用 JSON.parse

下面两种是可以兼容所有浏览器的方法:

通过 location.hash 传送

这种方法把数据放在 location.hash 中。例如 http://a.com 中嵌入一个页面 http://b.com/test.html,两者之间通信,如果 a.com 要向 b.com 发送数据,只需要创建 iframe 时把数据放在 # 后,例如 http://b.com/test.html#somedata,后者通过 location.hash 便可读到数据 somedata。反过来 iframe 向 a.com 发送数据,则需要借助代理页,这个代理页位于 a.com 下,负责将数据回传给主页面,因为是同一个域名下,因此可以与主页面通信。

需要注意这里的数据都必须先使用 encodeURIComponent 进行编码。示例:

// 主页面 a.com
var ifr = document.createElement("iframe"), msg = "Hello, this is a.com";
ifr.style.display = "none";
ifr.src = "http://b.com/test.html#" + encodeURIComponent(msg);
document.body.appendChild(ifr);
window.receive = function(data) {
  console.log(data);
}

// 页面 http://b.com/test.html
if(location.hash) {
  console.log("Got a message:" + decodeURIComponent(location.hash.slice(1)));
}
var msg = "Hello, this is b.com", ifr = document.createElement("iframe");
ifr.style.display = "none";
ifr.src = "http://a.com/proxy.html#" + encodeURIComponent(msg);  // 代理页
document.body.appendChild(ifr);

// 代理页 proxy.html
if(location.hash) {
  top.receive(decodeURIComponent(location.hash.slice(1)));
}

通过 window.name 传送

window.name 是一个字符串,可以保存最多 2M 左右的数据,之所以使用 window.name,是因为在一个 iframe 或窗口中,不管页面地址如何改变,window.name 都一直保持不变。所以 iframe 可以先将数据保存在 window.name,然后跳转至 about:blank,因为主页面和 about:blank 之间不受同源策略限制,便可以读取 window.name。主要通过监听 load 事件来实现,示例:

// 主页面 a.com
var ifr = document.createElement("iframe");
ifr.style.display = "none";
ifr.onload = function() {
  if(ifr.contentWindow.name) {  // 受到限制时则会返回 undefined
    console.log(ifr.contentWindow.name);
  }
};
ifr.src = "http://b.com/test.html";
document.body.appendChild(ifr);

// 页面 http://b.com/test.html
var msg = "Hello, this is b.com";
window.name = msg;
location.replace("about:blank");
还没有评论,沙发空缺中……
flight