同源策略
只有满足同源的脚本才可以获取资源,这样确实保证了网络上的安全性,但是限制了资源之间的互相利用,比如ajax,ajax是通过url来获取数据,同样也会受到同源策略的限制。
解决跨域问题的方法
- flash。很久之前的方法,目前基本弃用;
- 服务器代理中转。服务器不会受到同源策略的限制。
- iframe+document.domain。这种方法只能针对主域名相同的子域之间。
- iframe+location.hash。
- iframe+window.name。
- HTML5 postMessage方法。
- jsonp。这个是现在主要使用的。
document.domain+iframe
只有在主域相同而子域不同的情况下这种方式才可以使用。
子域:
以百度为例,百度的主网页是www.baidu.com,而下面还有很多像zhidao.baidu.com、news.baidu.com等,这些都可称为www.baidu.com这个主域下的子域。
具体做法:
具体的做法是:在http://www.a.com/a.html和http://script.a.com/b.html两个文件中分别加上document.domain=”a.com”,然后通过a.html创建一个iframe,然后去控制iframe的contentDocument,这样两个js文件之间就可以“交互”了。
// a.html
<script type="text/javascript">
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
// 在这里操纵b.html
alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};
</script>
// b.html
<script type="text/javascript">
document.domain = 'a.com';
</script>
备注:页面的domain默认等于window.location.hostname。主域名是不带有www的域名,例如baidu.com,doamin只能设置为主域名。
缺点
1. 安全性。当一个站点被攻击后,另一个站点就会引起安全漏洞。
2. 操作繁琐。如果一个页面中引入多个iframe,要想能够操作所有的iframe,必须都得设置相同的domain。
iframe+location.hash
原理
利用location.hash来进行传值。在一个url:http://www.baidu.com#hello;其中的#hello就是通过location.hash来设置的,改变hash值并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然,这个东西既然放到了url里面,自然就会有长度的限制。
假设域名a.com下的文件demo1.html要和b.com下的demo2之间传递信息,demo1.thml首先要自动创建一个隐藏的iframe,iframe的src指向b.com下的这个demo2.html网页,这个时候,hash值就可以作为参数传递了。
demo2.html响应请求之后,通过修改demo1.html的hash值来传递数据。
需要注意:当两个页面不在同一个域名下的时候,IE、chrome不允许修改parent.location.hash的值,所以要借助a.com域名下的一个代理iframe。Firefox中可以修改。
// demo1.html
<button id="btn">Click me to get message</button>
<script type="text/javascript">
var btn = document.getElementById('btn'),
ifr = document.createElement('iframe');
ifr.src = 'http://localhost/2.html';
btn.onclick = function() {
location.hash && console.log(location.hash);
};
document.body.appendChild(ifr);
</script>
// demo2.html
<script type="text/javascript">
try {
parent.location.hash = 'hello?';
}catch (e) {
// ie、chrome的安全机制无法修改parent.location.hash,
// 所以要利用一个中间的和1.html同域下的代理iframe
location.hash = "helllo?";
var tempIfr = document.createElement('iframe');
tempIfr.style.display = 'none';
tempIfr.src = "temp.html";
document.body.appendChild(tempIfr);
}
</script>
// temp.html 可在本地服务器模拟
<script type="text/javascript">
//这里的parent是2.html,也就是我们要传递数据的页面
//parent.parent是主页面
parent.parent.location.hash = parent.location.hash;
</script>
缺点:
数据直接暴露在url中,数据的长度和类型都有限制。
iframe+window.name
原理:
与location.hash方法差不多,也是主页面有一个iframe,然后通过修改子页面的window.name来让主页面获取到这个值。
需要三个页面
1.htmll 主页面;
temp.html 代理页面,通常没有任何html内容,要和主页面在同一个域名下;
2.html 数据页面;
在主页面1.html下创建一个iframe,把它的src指向数据页面2.thml,数据页面会把数据附加到这个iframe的window.name上。
// 1.html主页面
<button id="btn">Click me to get message</button>
<script type="text/javascript">
var btn = document.getElementById('btn'),
hasGetData = false;
ifr = document.createElement('iframe');
ifr.src = 'http://localhost/2.html';
btn.onclick = function() {
if (hasGetData) {
console.log(ifr.contentWindow.name);
document.body.removeChild(ifr);
}else {
ifr.contentWindow.location = 'http://localhost/temp.html';
hasGetData = true;
}
};
document.body.appendChild(ifr);
</script>
2.html
<script type="text/javascript">
window.name = 'hello!';
</script>
在主页面中,最开始让iframe的src指向了2.html这个页面,获取了它里面的window.name的值,但是这个时候如果在真的服务器上的话,因为域名不同所以无法直接传值。
当第一次点击了按钮之后,把iframe的locatin值换成了和1.html同域名的temp.html,这两个同域名的网页之间可以互相访问,而且此时我们也没有改变刚才的name的值,这样就把两个不同域名的数据通过一个中间页面的形式传递过来了。
获取数据以后销毁这个iframe,释放内容,同时保证安全性。
总结:利用iframe的src属性实现让外域转向本地域,这样数据就由iframe的window.name从外域传递到了本地域。这样既巧妙地绕过了浏览器的跨域问题,又能保证安全性。
缺点:通信的数据量很小。
HTML5 postMessage方法
H5中有一个功能就是跨文档消息传输。主流浏览器的较高版本包括IE8和IE8以上都实现了这个功能。而且facebook已经使用了这个功能,用postMessage支持web的实时消息传递。
getMessageHTML.postMessage(message, targetOrigin)
;
其中getMessageHTML
是我们对于要接受信息的页面的引用,可以是iframe的contentWindow属性、window.open的返回值、通过name或下标从window.frames取到的值。
message
是我们所要发送的信息,字符串类型。
targetOrigin
是用于限制getMessageHTML
的,当填“*”的时候是不做限制。
// 1.html
<iframe id="ifr" src="2.html"></iframe>
<button id="btn">Click me to change color</button>
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.onclick = function() {
var ifr = document.getElementById('ifr');
ifr.contentWindow.postMessage('black', '*');
};
</script>
// 2.html
<div id="div"></div>
<script type="text/javascript">
var div = document.getElementById('div');
window.addEventListener('message', function(event){
div.style.backgroundColor = event.data;
}, false);
</script>
点击click按钮之后,iframe中那个红色的方块就会变成黑色,这样就完成了一个最基本的实时通信功能。不过这里注意本来的2.html里面的那个div的颜色并没有改变。
服务器代理中转
因为服务器不受同源策略的限制,所以只要把资源全部放到自己的服务器上,然后让这个服务器上的网页获取这些资源就可以了。
重点:jsonp
原理:web页面上script引入js文件不受跨域的影响,不仅如此,凡是有src属性的标签都不受同源策略的影响。
通过这个特性,可以把资源直接放到script标签的src里面,这样就把数据放到了服务器上,并且是使用json的形式。(js可以轻松操作json数据)
但是由于无法监控script的src的加载状态,不知道数据有没有获取完成,所以要事先定义好处理函数。
<script type="text/javascript">
function success (data) {
console.log(data.name + ' ' + data.age);
}
</script>
然后在数据里面开头写上这个函数名,引入进来之后等到文件全部加载完毕,刚好就是调用已经定义好的函数,并且参数就是后面的那一串数据。
在这之前,先创建一个txt文件,里面写上:
// txt 文件
success({“name”: “Mandy”, “age”: 18})
然后在html文件中写上:
<script type="text/javascript">
function success (data) {
console.log(data.name + ' ' + data.age);
}
</script>
<script type="text/javascript" src="./test.txt"></script>
// 最后打印Mandy 18
在实际开发中,script标签的src属性的格式是“url+参数”的形式,比如“url?cb=doJSON”,这里的cb是和后台人员协商好的类似形参的东西,是一个留给我们写处理函数的接口,后面的doJSON就是我们事先定义好的处理函数。
模拟百度搜索栏
检测百度网页的资源文件,发现当在输入栏中输入文字的时候,下面出来的那些关键字信息都是存放在http://suggestion.baidu.com/su?wd=输入值&cb=处理函数,这个url里面的,而且主要是在这个对象里面的s属性上。
所以我们可以直接在input标签的keyup上面绑定这样一个事件:
在input标签的keyup上面绑定事件:
window.onload = function(){
var $Input = document.getElementById('q');
var $Ul = document.getElementById('ul1');
$Input.onkeyup = function(){
if(this.value != ''){
var $Script = document.createElement('script');
$Script.src = 'http://suggestion.baidu.com/su?wd=' + this.value + '&cb=doJSON';
document.body.appendChild($Script);
}else{
$Ul.style.display = 'none';
}
}
}
处理函数:
点击每一个显示出来的关键字信息的时候,都会跳转到一个新页面,而这些新页面的链接都是https://www.baidu.com/s?wd=刚才的输出值出来的对象的s属性的值,因此处理函数可以写成:
function doJSON(data){
var $Ul = document.getElementById('ul');
var html = '';
if (data.s.length){
$Ul.style.display = 'block';
for(var i = 0; i < data.s.length; i++){
html += '<li><a href="https://www.baidu.com/s?wd=' + data.s[i] + '" target="_blank">' + data.s[i] + '</a></li>'
}
$Ul.innerHTML = html;
}else{
$Ul.style.display = 'none';
}
}
一个简陋的百度搜索栏就完成了。