引言
为什么会有跨域,什么是跨域,这些疑问在没有接触它之前都是正常的。简单的来讲,是因为某些人利用加载外部域的外部脚本代码来搜集原网站信息,以致各种信息泄露,财产损失等现象发生。所以javascript出于安全的角度,提出同源策略,禁止加载不同域下的资源。但是我们开发人员有时的确也有需要使用外部资源,怎么办呢?这就是跨域。
1.什么是跨域
域名的组成:
判定方法:
- 1.当协议,子域名,主域名,端口号四个之间任意一个不同时,都算不同域
- 2.不同域名之间请求资源就算跨域
示例:
//请求不同的资源
http://www.baidu.com/index.html http://www.baidu.com/1.png 非跨域
//协议不同
http://www.baidu.com/index.html https://www.baidu.com/index.html 跨域
//子域名不同
http://www.baidu.com/index.html http://bbs.baidu.com/index.html 跨域
//主域名不同
http://www.baidu.com/index.html http://www.sina.com/index.html 跨域
//端口号不通
http://www.baidu.com:80/index.html http://www.baidu.com:81/index.htm 跨域
2.跨域方法一: document.domain
首选得知道document.domain是什么?
释义:document.domain返回当前文档的域名,可以试试,我用的bing搜索,没有广告哦。
可以打印那么可以赋值修改domain吗?试试就知道了
可见document.domain修改是有原则的,具体有两条
- 可以修改为本身,相当于没做修改(感觉被耍了)
- 可以修改为本身的主域名(跨域就从这里做文章)
这么讲,假设你们班上没有同名的人。你叫李小明,别人可以叫你李小明,也可以叫你小明,不可能叫你李大明,周小明。老师提问,叫到李小明,你肯定一下子就站起来了。如果恰好你们班上有个人叫王小明,那老师叫小明,谁又该站起来呢。所以跨域也是这个道理,利用这种歧义,让“名字”相同,“姓氏”不同的人,实现共享信息
举个例子:www.baidu.com下面www是子域名,baidu.com是主域名。所以要修改,只有两种可能,其它情况都不行
- 第一,修改为www.baidu.com
- 第二,修改为baidu.com
我们实现跨域主要是依靠第二个结果来做文章。所以document.domain只对主域名相同,子域名不同的情况实现跨域。为什么这样讲?看看例子。
准备好两个页面:www.a.com cn.a.com —> 两个域名的主域名都是a.com,子域名不同
实现方式:借助Frame对象
www.a.com a.html页面
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://cn.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
//获取Frame对象文档内容
var doc = ifr.contentDocument || ifr.contentWindow.document;
// 在这里操纵b.html
alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeVaue);
};
cn.a.com b.html页面
document.domain = 'a.com';
2.跨域方法二:jsonp
这里就不讨论jsonp和json的区别了,就像java和javascript名字相似,其实质是两种东西。
jsonp其实就是利用script标签不受同源策略限制为出发点的。像下面这样
//jquery CDN服务,引用不同域文件完全不受同源策略限制
<script src="//cdn.bootcss.com/jquery/3.1.1/core.js"></script>
我们先构建jsonp的雏形
A域下有个a.html,B域下有个b.js
A域下a.html页面内容为:
<script type="text/script">
function info(my){
console.log(my.info);
}
</script>
<script type="text/script" src="B/b.js"></script>
B域下b.js的内容为:
info({info:"kingboss"});
结果为:kingboss
所以jsonp的实质是,另一个域下的文件传递一个callback执行函数,函数名与原域所定义的函数名相同。
当然世界是多变的,代码是活的,所以我们可以动态的创建script标签,引入可变的src地址
A域下a.html页面内容改为:
<script type="text/script">
function info(my){
console.log(my.info);
}
function addScript(src){
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function(){
addScript("B/b.js");
}
</script>
B域下b.js的内容不变:
info({info:"kingboss"});
有人就会问了,加入B域下的文件不知道A域下的函数名怎么办?简单,那就让A域下的文件告诉B就ok了,具体的告知方式就是在url部添加callback参数
A域下a.html页面内容改为:
<script type="text/script">
function info(my){
console.log(my.info);
}
function addScript(src){
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function(){
addScript("B/b.js?callback=info");
}
</script>
B域下的文件根据callback参数动态生成执行函数
jquery的jsonp实现上面的例子
<script src="//cdn.bootcss.com/jquery/3.1.1/core.js"></script>
<script type="text/javascript">
$(function(){
$.ajax({
type: "get",
async: false,
url: "B/b.js",
dataType: "jsonp",
jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
success: function(my){
console.log(my.info)
},
error: function(){
alert('fail');
}
});
})
</script>
注意:jsonp只支持get请求,原因不是很清楚
3.跨域方法三:location.hash
前面我们讲过document.domain只适用于主域名相同,子域名不同的情况,那么主域名也不同呢?又该怎么办。所以就有了进阶版,location.hash传值方法。
什么是location.hash,其实它是js用来管理内部地址的对象,内容为:
属性 | 描述 |
---|---|
hash | 从井号 (#) 开始的 URL(锚) |
host | 主机名和当前 URL 的端口号 |
hostname | 当前 URL 的主机名 |
href | 完整的 URL |
pathname | 当前 URL 的路径部分 |
port | 当前 URL 的端口号 |
protocol | 当前 URL 的协议 |
search | 从问号 (?) 开始的 URL(查询部分) |
方法:利用location.hash进行传值,选择其传值的理由是改变hash指不会刷新页面。A域下a.html通过创建iframe指向B域下b.html,由于不同域,所以B域下会创建A域的a1.html(代理页面),代理页面a1.html和a.html同属一个域,因此可以修改。缺点:数据完全暴露在url中,容量也有限制
逻辑图
A域下 a.html
<script type="text/javascript">
function startRequest(){
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'B/b.html#data';
document.body.appendChild(ifr);
}
function checkHash() {
try {
//获取hash传入的data值
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域下 b.html
<script type="text/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,
// 所以要利用一个中间的B域下的代理iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'A/a1.html#somedata'; // 注意该文件在A域下
document.body.appendChild(ifrproxy);
}
}
</script>
A域下 a1.html
<script type="text/javascript">
//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
</script>
4.跨域方法四:window.name
先了解一下什么是window.name,以及它有什么特性。window.name方法传值其实和location.hash类似,也依靠代理文件实现。共需要三份文件,A域下的a.html;B域下的b.html;A域下的a1.html(空代理文件)
B域下的b.html
<script type="text/javascript">
window.name = 'data'; // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
</script>
A域下的a.html
<script type="text/javascript">
var state = 0,
//动态地创建iframe
iframe = document.createElement('iframe'),
iframe.src = 'B/b.html';
document.body.appendChild(iframe);
loadfn = function() {
if (state === 1) {
var data = iframe.contentWindow.name; // 读取数据
alert(data); //弹出'data'
} else if (state === 0) {
//改变iframe的location指向代理文件
iframe.contentWindow.location = "A/a1.html"; // 设置的代理文件
}
};
//兼容,能力检测
if (iframe.attachEvent) {
iframe.attachEvent('onload', loadfn);
} else {
iframe.onload = loadfn;
}
//销毁iframe
iframe.contentWindow.document.write('');//文档内容清空
iframe.contentWindow.close();//关闭子窗口
document.body.removeChild(iframe);//去除tag
</script>
疑问:
1.什么是window.name?
解释:name 属性可设置或得到窗口的名称,其值为字符串;该名称是在 open() 方法创建窗口时指定的或者使用一个 frame 标记的 name 属性指定的,默认情况下 name 属性值是为空的。窗口的 name 属性可以用于a或form标签的 target 属性值,这样表示超链接文档或表单提交结果应该显示于指定 name 的窗口或框架中。
特点是:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。—->这也解释了为什么A域的代理文件可以访问到B域的window.name
2.什么是contentWindow属性?
contentWindow : 兼容各个浏览器,可取得子窗口的 window 对象。
contentDocument : Firefox 支持,> ie8 的ie支持。能够以 HTML 对象来返回 iframe 中的文档。可以通过所有标准的 DOM 方法来处理被返回的document对象
解决了上面的疑惑,用图来呈现此逻辑
结束语
虽然跨域有很多种方法,但是要折中,比如引入iframe本身就对网页优化有一定的影响,还有location.hash的安全漏洞等问题。所以不要滥用。