# 什么情况会跨域
- 主域不同
- 主域相同,子域不同
- 域名相同,协议不同(http / https)
- 域名相同,端口不同
# 跨域解决办法
# 1. document.domain + iframe
对于主域相同而子域不同的例子,可以通过设置 document.domain 的办法来解决。具体的做法如下:
是可以在 a.com/a.html 和 1.a.com/b.html 两个文件中分别加上 document.domain = 'a.com';
然后通过 a.html 文件中创建一个 iframe,去控制 iframe 的 contentDocument,当然这种办法只能解决主域相同而二级域名不同的情况。
<!-- a.com/a.html -->
<iframe id='i' src="1.a.com" onload="do()"></iframe>
<script>
document.domain = 'a.com';
document.getElementById('i').contentWindow;
</script>
<!-- 1.a.com/b.html -->
<script>
document.domain = 'a.com';
</script>
这样,就可以解决问题了。值得注意的是:document.domain 的设置是有限制的,只能设置为页面本身或者更高一级的域名。利用这种方法是极其方便的,但是如果一个网站被攻击之后另外一个网站很可能会引起安全漏洞。
# 2. 动态创建 script (jsonp)
JSONP
的全称是 "JSON With Padding", 词面意思上理解就是 "填充式的JSON"。由于浏览器的同源策略,使得在网页端出现了这个“跨域”的问题,然而我们发现,所有的 src 属性并没有受到相关的限制,比如 img / script 等,jsonp 就是利用了 script
可以执行其它域的 js 函数,比如这样:
<!-- a.com/a.html -->
...
<script>
function callback(data) {
console.log(data.url)
}
</script>
<script src='http://b.com/b.js'></script>
...
<!-- b.com/b.js -->
callback({url: 'http://www.rccoder.net'})
利用这一点,假如 b.js 里面的内容不是固定的,而是根据一些东西自动生成的, 嗯,这就是 JSONP 的主要原理了。回调函数+数据就是 JSON With Padding 了,回调函数用来响应应该在页面中调用的函数,数据则用来传入要执行的回调函数,至于这个数据是怎么产生的,无非就是服务端的字符串拼接了。
以下是 jsonp 请求的实现:
var loadJsonp = (function() {
var seq = new Date() * 1;
return function(url, params, callback) {
var funName = 'XYJsonp' + seq++,
head = document.getElementsByTagName('head')[0],
script = document.createElement('script');
for (var key in params) {
url += (/\?/.test(url) ? '&': '?') + key + '=' + encodeURIComponent(params[key]);
}
url += '&callback=' + funName;
window[funName] = function(data) {
window[funName] = undefined;
try {
delete window[funName];
}
catch(e) {}
if (head) {
head.removeChild(script);
}
callback(data);
};
script.charset = "UTF-8";
script.src = url;
head.appendChild(script);
};
} ());
注意:JSONP的这种实现方式不受同源策略的影响,兼容性也很好;但是它之支持 GET 方式的请求,只支持 HTTP 请求这种特殊的情况,对于两个不同域之间两个页面的互相调用也是无能为力。
# 3. 利用 iframe 和 location.hash
这个办法比较绕,把数据的变化显示在 url 的 hash 里面。原理是利用 location.hash 来进行传值。但是由于 chrome 和 IE 不允许修改 parent.location.hash 的值,所以需要再加一层。
a.html 和 b.html 进行数据交换:
<!-- a.com/a.html -->
<script>
function startRequest() {
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'http://b.com/b.html#paramdo';
document.body.appendChild(ifr);
}
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1) : '';
if (console.log) {
console.log('Now the data is '+data);
}
}
catch(e) {};
}
setInterval(checkHash, 2000);
</script>
<!-- b.com/b.html -->
<script>
// 模拟一个简单的参数处理操作
switch(location.hash){
case '#paramdo':
callBack();
break;
case '#paramset':
//do something……
break;
}
function callBack() {
try {
parent.location.hash = 'somedata';
}
catch (e) {
// ie、chrome 的安全机制无法修改 parent.location.hash,
// 所以要利用一个中间域下的代理 iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://a.com/c.html#somedata'; // 注意该文件在 a.com 域下
document.body.appendChild(ifrproxy);
}
}
</script>
<!-- a.com/c.html -->
<script>
// 因为 parent.parent 和自身属于同一个域,所以可以改变其 location.hash 的值
parent.parent.location.hash = self.location.hash.substring(1);
</script>
# 4. window.name 实现跨域数据传输
window.name
在一个窗口(标签)的生命周期之内是共享的,利用这点结合 iframe:当在 iframe 中加载新页面时,name
的属性值依旧保持不变。
总结起来即:iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
需要3个文件:a.com/a.html, a.com/proxy.html, b.com/b.html
<!-- a.com/a.html -->
<script type="text/javascript">
var state = 0,
iframe = document.createElement('iframe'),
loadfn = function() {
if (state === 1) {
var data = iframe.contentWindow.name; // 读取数据
alert(data); //弹出'I was there!'
}
else if (state === 0) {
state = 1;
iframe.contentWindow.location = "http://a.com/proxy.html"; // 设置的代理文件
}
};
iframe.src = 'http://b.com/b.html';
if (iframe.attachEvent) {
iframe.attachEvent('onload', loadfn);
}
else {
iframe.onload = loadfn;
}
document.body.appendChild(iframe);
</script>
<!-- b.com/b.html -->
<script type="text/javascript">
window.name = 'I was there!';
// 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
// 数据格式可以自定义,如json、字符串
</script>
proxy 是一个代理文件,空的就可以,需要和 a 在同一域下
# 5. 利用 HTML5 postMessage
window.postMessage
是 HTML5 新增 API 之一,可以用来向其他所有的 window 对象发送消息。需要注意的是我们必须要保证所有的脚本执行完才发送 MessageEvent,如果在函数执行的过程中调用了他,就会让后面的函数超时无法执行。
下一代浏览器支持这个功能 Chrome 2.0+, Internet Explorer 8.0+, Firefox 1.0+, Opera 9.6+, 和 Safari 4.0+ 。
参考资料:
# 6. CORS
CORS 的全称是 Cross-Origin Resource Sharing,即跨域资源共享。他的原理就是使用自定义的 HTTP 头部,让服务器与浏览器进行沟通,主要是通过设置响应头的 Access-Control-Allow-Origin 来达到目的,这样,XMLHttpRequest 就能跨域了。
参考资料: