前言
本人开始接触前端有一年左右,期间遇到了许多问题,有些问题在当下被解决之后,再次遇到还是会有些模糊和不解。与其说是新人百问,不如说是我自己的百问,希望能整理出来这些知识供自己回顾,也供大家学习和指点。
什么是跨域
在前端部分,我们通常所说的跨域指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
例如:页面A想获取页面B的资源,如果A、B页面的协议、域名、端口不同,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源。
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
同源策略限制了以下行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 JS 对象无法获取
- Ajax请求发送不出去
发生跨域的三个必要条件
- 浏览器限制: 即浏览器对跨域行为进行检测和阻止;
- 触发跨域的三要素之一:即 协议,域名和端口三个条件满足其一
- 发起的是xhr请求: 即发起的是XMLHttpRequest类型的请求。
其实xhr请求才是设计者们设计跨域的最关键的条件因素。并且只有同时满足三个条件才能触发跨域问题。
常见的跨域场景
http://www.baidu.cn/index.html 调用 http://www.baidu.cn/server.php 非跨域
http://www.baidu.cn/index.html 调用 http://www.google.cn/server.php 跨域,主域名不同
http://www.baidu.cn/index.html 调用 http://script.baidu.cn/server.php 跨域,主域相同,子域名不同
http://www.baidu.cn:8080/index.html 调用 http://www.baidu.cn/server.php 跨域,端口不同
https://www.baidu.cn/index.html 调用 http://www.baidu.cn/server.php 跨域,协议不同
localhost 调用 127.0.0.1 跨域
解决跨域的常见方法
1.jsonp解决跨域问题
比如你有一个页面index.html,需要从不同的域里获取json数据,数据地址为“http://demo.com/data.php”,那么你就可以在html页面中这样写:
<script>
function do(jsondata){
//处理数据
}
</script>
<script src="http://demo/data.php?callback=do"></script>
可以看出jsonp的原理就是利用script引入js文件,文件加载成功后执行url中指定的函数,我们需要获取的json数据将会作为参数传入。不过如果数据地址是别人的文件,自己无法操控,那么就得按照提供数据的一方的数据模式来处理。所以使用jsonp方法处理跨域,是需要服务器端的配合。
也可以使用jquery封装$.getJSON()的方法,这个方法很便利,看一下代码:
<script>
$.getJSON('http://demo/data.php?callback=?’,function(jsondata){
//处理数据
});
</script>
jquery会自动生成一个全局函数来替换callback=?中的问号,获取到数据后又会自动销毁。$.getJSON方法会自动判断是否跨域,不跨域,就调用普通的ajax方法;跨域,则会以异步加载js文件的形式来调用jsonp的回调函数。
再以我们常用的AJAX方法为例:
$.ajax({
url:"",
dataType:'jsonp',
data:'',
jsonp:'callback', //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)
success:function(result) {
//成功的处理
},
error:function(){
//错误处理
}
});
jsonp的缺点:
- JSONP是一种非官方的方法,而且这种方法只支持GET方法,不如POST方法安全。。
- JSONP的实现需要服务器配合,如果是访问的是第三方的服务器,我们没有修改服务器的权限,那么这种方式是不可行的。
2.window.name+iframe解决跨域
window.name 的美妙之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,我们可以利用这一特性来解决跨域问题。
例如:我们想从从www.example.com/a.html中获取www.demo.com/b.html中的数据,我们可以用一个隐藏的iframe标签来加载b页面,再在a页面里获取iframe的数据。
首先,在a.html中包含:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>解决跨域问题</title>
<style type="text/css">
#ifarme{
display:none;
}
</style>
</head>
<body>
<iframe src="http://www.demo.com/b.html" frameborder="0" onload="getData()" id="ifarme"></iframe>
</body>
<script type="text/javascript">
function getData(){
var iframe = document.getElementById("iframe");
iframe.onload = function(){
var data = iframe.contentWindow.name;
console.log(data);
}
ifarme.src="about:blank";//这里的src的页面可以随意设置为与a.html同源的页面,空白页about:blank也行。只有同源,a.html才能访问到iframe里面的东西。
}
</script>
</html>
接着,在b.html中包含:
<script>
window.name="a页面需要获取的json数据或者字符串";
</script>
这样就解决了跨域问题。
3.postMessage解决跨域
postMessage 是 HTML5 新增加的一项功能,跨文档消息传输(Cross Document Messaging),目前:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 都支持这项功能,使用起来也特别简单。
还是以刚才的例子来实现
首先,在a.html中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>a.html</title>
</head>
<body>
<iframe src="http://www.demo.com/b.html" style='display: none;'></iframe>
<script>
window.onload = function() {
let targetOrigin = 'http://www.demo.com';
window.frames[0].postMessage('我要给你发消息了!', targetOrigin);
}
window.addEventListener('message', function(e) {
console.log('a.html 接收到的消息:', e.data);
});
</script>
</body>
</html>
接着,在b.html中:
<script>
window.addEventListener('message', function(e) {
if(e.source != window.parent) {
return;
}
let data = e.data;
console.log('b.html 接收到的消息:', data);
parent.postMessage('我已经接收到消息了!', e.origin);
});
</script>
总结
跨域是工作中经常会遇到的问题,也是非常多前端岗位的必问题目之一,解决跨域的方法还有很多,笔者能力有限,目前只掌握这三种方法,有待补充。