本文首次发布于 tinypoint`s blog, 作者 @张小点(tinypoint) ,转载请保留原文链接.
前言
最近正是招聘季节,面试前端的小伙伴们难免会在跨域问题上被面试官们’刁难’,不过别怕,这篇文章将会详细的讲解常用的跨域解决方案,帮你在面试中披荆斩棘。

正文
通过XHR实现AJAX通信的一个主要限制,来源于跨域安全策略。默认情况下XHR对象只能访问与包含它的页面位于同一个域中的资源,即同源策略。这种安全策略可以预防某些恶意行为。但是,实现合理的跨域请求对开发某些浏览器应用程序是至关重要的。
同源策略
同源是指
- 协议相同(FTP、HTTP等)
- 域名相同(包括每一级域名, domain.com和www.domain.com不同)
- 端口相同
不符合以上任意一条都不属于同源, 跨域的请求将会受到限制
解决方案
所有具有src的标签
- 比如
<img>,<script>,<link> - 只能进行GET请求
- 需要注意的是img标签在设置src属性的时候就会进行发送请求,而link,script,iframe等则是在被添加到页面中才会发送请求
var img = new Image()
img.onload = function () {
console.log('成功加载');
}
img.src = 'http://www.example.com';
var script = document.createElement('script');
script.src = 'http://www.example.com'
document.body.appendChild(script);
jsonp
- jsonp其实利用了
- 回调函数必须是全局函数
- 同样,只能用于GET方法
function dataResolver (data) {
console.log('This is' + data);
}
var script = docuemnt.createElement('script');
script.src = 'http://www.example.com?callback=daraResolver'
docuemnt.body.appendChild(script);
document.domain
- 只适用于主域相同,子域不同的页面交流,如从
http://subdomain.domain.com到http://domain.com之间的通信 - 原理:利用document.domain允许同时也是只允许从低级域名到高级域名设置,手动设置document的domain属性,让页面同域
<script>
// http://domain.com/example 页面
var iframe = document.createElement('iframe');
iframe.src = 'http://subdomain.domain.com';
iframe.onload = function() {
var iframeDocuemnt = iframe.contentDocument || iframe.contentWindow.document;
alert(iframeDocuemnt.getElementById("foo").innerHTML));
};
iframe.style.display = 'none';
document.body.appendChild(iframe);
<script>
设置document.domain与父页面保持一致
<script>
// http://subdomain.domain.com/example 页面
document.domain = 'domain.com';
</script>
window.name跨域
- 在一个窗口(window)的生命周期内,窗口载入的所有的页面共享一个window.name的,每个页面对window.name都有读写的权限
- window支持多达
2m的name属性
当a.com/index.html要从b.com/data.html获取数据时,需要借助与a同域的a.com/proxy.html作为代理
a.com/index.html
// load事件会触发两次,每个时机都有不同的任务
var proxy = function (url, cb) {
var isFirst = true;
var iframe = document.createElement('iframe');
iframe.src = url;
iframe.onload = function () {
if (isFirst) {
// 切换到代理页面
iframe.contentWindow.location = 'http://a.com/proxy.html';
isFirst = false
} else {
// 读取window.name
cb(iframe.contentWindow.name);
destoryFrame();
}
}
}
// iframe用完要删除,释放内存,同时保证了安全
function destoryFrame () {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
//想要发送数据,可以将数据放在window.name中
window.name = '12';
proxy('http://b.com/data.html', function (data) {
console.log(data);
});
a.com/proxy.html代理页面与主页面同域,内容为空即可
b.com/data.html
var id = window.name;
window.name = id + 'is tinypoint';
location.hash
- 不同域利用location.hash来传值
- 通过hashchange事件来监听hash变化
a.com下的a.html想要与b.com下的b.html通信,需要中间页a.com下的c.html来实现
a.html
<iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 要发送的数据放在b页面的hash上
iframe.src = iframe.src + '#name';
// 回调方法
function dataResolver(data) {
alert('This is ' + data);
}
</script>
b.html
<iframe id="iframe" src="http://www.a.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 监听a.html传来的hash值,再传给c.html
window.onhashchange = function () {
var name = location.hash
iframe.src = iframe.src + '#tinypoint';
};
</script>
c.html
<script>
// 监听b.html传来的hash值
window.onhashchange = function () {
// 再通过操作同域a.html的js回调,将结果传回
window.parent.parent.dataResolver('name is ' + location.hash);
};
</script>
postMassage
HTML5中XMLHttpRequest 2级中的API,可解决以下问题
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
用法:postMessage(data,origin)方法接受两个参数 data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。 origin: 协议+主机+端口号,也可以设置为”*”,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/”。
a.html:(http://www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};
// 接受domain2返回数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
b.html:(http://www.domain2.com/b.html)
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
// e.data中保存了传递的数据
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>
跨域资源共享(CORS)
- 使用自定义的http头部要让浏览器与服务器进行通信
- 在后端设置Access-Control-Allow-Origin: 当前域名(公共资源,可设置为*)
IE使用XDR(XDomainRequest)对象实现CORS
XDR与XHR类似
- 有open,send,abort方法,但是open只接受两个参数,即所有请求都是异步的
- 支持load,error,timeout事件
- 支持timeout
XDR与XHR有一些不同之处
- cookie不会随请求发送,也不会随相应返回
- 只能设置请求头部信息的Content-Type字段
- 不能访问响应头部信息
- 只支持GET和POST请求
- 不能访问status和statusText属性
var xdr = new XDomainRequest();
xdr.onload = function () {
console.log(xdr.responseText);
}
xdr.onerror = function () {
console.log('An error occurred');
}
xdr.open('get', 'http://www.a.com');
xdr.send();
其他浏览器对CORS的支持
- 只需要在open方法中传入绝对路径即可
- 存在一些限制
- 不能使用setRequestHeader()设置自定义头部
- 不能收发和接受cookie
- getAllResponseHeaders()总是返回空字符串
Web Sockets
ws协议是HTML5新协议。实现了浏览器与服务器全双工通信,同时允许跨域通讯。 支持的事件
- message 收到服务器传来的信息时触发
- open 成功建立里连接时触发
- error 发生错误时触发,连接不能持续
- close 连接关闭时触发 ```html
socket.send(‘some text’);
socket.onmessage = function (e) { console.log(e.data) } </script> ```
参考