js跨域的四种方法(带实例)
Table of Contents
一、使用jsonp跨域
1、使用jQuery跨域
html代码
<!DOCTYPE html>
<html>
<head>
<title>jsonp跨域一:使用jQuery跨域</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
<div style="text-align:center;"><input id="getData" type="button" value="获取数据" /></div>
</body>
</html>
对应的js代码
//jquery入口
$(document).ready(function(){
$('#getData').click(function(){
$.ajax({
type:'get', //这里只能用get,因为jsonp没有post的说法
url:'http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo1.php',
// url:'http://45.124.65.84/data.txt',
dataType:'jsonp',
data:{
name:'Bruce',
mobile:13838383388,
},
//指定访问的url的参数名称(用于告诉服务器客户端回调函数的函数名)
jsonp:'callback', //(如果不写该属性,则默认为callback)
//指定访问的url的参数值(其实就是回调函数名)
jsonpCallback:'testfunc', //(如果不写该属性,则默认为jquery自动产生一个随机名)
//以上jsonp与jsonpCallback两个参数结合起来,代表:你在服务器端用 $_GET['callback'],获取到的值为testfunc,即相当于url上传了这样的参数 ?callback=testfunc。
success:function(data){
var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
+'<br>手机号:'+ data.mobile
+'<br>年龄:'+ data.age
+'<br>性别:'+ (data.gender==1 ? '男' : '女')
+'<br>头像:'+ '<img src="'+data.avatar+'">';
+'<br></div>';
$('#screen').append(str);
}
});
});
});
服务器端代码:cross_domain_demo1.php
<?php
//假设以下数组是从数据库中查询出来的数据
$userinfo = [
'id'=>2345,
'username'=>'Bruce',
'mobile'=>'13838383388',
'age'=>18,
'gender'=>1,
'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
];
$result = json_encode($userinfo);
//动态执行回调函数
$callback = $_GET['callback'];
sleep(5); //为了测试jquery ajax跨域工作原理,这里设置5秒后再返回数据
echo $callback."($result)"; //这句是关键
动态插入的<scrip>
标签:
返回的数据:
动图演示:
总结:
1、这种利用<script>
标签的src
属性来获取数据的方式我们叫jsonp方式,因为<script>
标签加载数据是不受『域』的限制的,所以可以用这种方法来做跨域请求。
2、服务器端需要返回一个对回调函数的调用,比如本例服务器返回的是以下数据,很明显这是在调用一个名字testfunc()
的函数,且传入了一个参数,这个参数是一个js对象,不过由于这是在jQuery里,testfunc其实就是jquery的success方法(具体jquery是怎么处理的就不用管了,看下面的原生js方式就明白了)
testfunc({"id":2345,"username":"Bruce","mobile":"13838383388","age":18,"gender":1,"avatar":"http:\/\/wx.qlogo.cn\/mmopen\/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR\/96"})
2、使用原生js跨域
html代码
<!DOCTYPE HTML>
<html>
<head>
<title>jsonp跨域二:使用原生js跨域</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<style>
#screen{word-wrap:break-word;word-break:break-all;}
</style>
</head>
<body>
<div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
<div style="text-align:center;"><input id="getData" type="button" value="获取数据" /></div>
</body>
</html>
对应的js代码
//回调函数
function testfunc(data){
//删除script标签
var head = document.getElementsByTagName("head")[0]; //获得head节点
var script = head.lastChild; //获得head的最后一个元素
if(script.nodeName=='SCRIPT'){ //如果这个元素是名是SCRIPT,则把它删除
head.removeChild(script); //删除这个script节点
}
//显示数据
var screen = document.getElementById('screen');
var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
+'<br>手机号:'+ data.mobile
+'<br>年龄:'+ data.age
+'<br>性别:'+ (data.gender==1 ? '男' : '女')
+'<br>头像:'+ '<img src="'+data.avatar+'">';
+'<br></div>';
screen.innerHTML = screen.innerHTML + str;
}
//点击获取数据,发起跨域请求
document.getElementById('getData').onclick = function(){
//动态创建一个script标签并把它加入到head里面
var script=document.createElement("script"); //创建一个script元素
script.type="text/javascript"; //给该元素添加type属性
script.src="http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo1.php?callback=testfunc"; //给该元素添加
document.getElementsByTagName("head")[0].appendChild(script);
}
服务器端php代码:cross_domain_demo1.php
<?php
//假设以下数组是从数据库中查询出来的数据
$userinfo = [
'id'=>2345,
'username'=>'Bruce',
'mobile'=>'13838383388',
'age'=>18,
'gender'=>1,
'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
];
$result = json_encode($userinfo);
//动态执行回调函数
$callback = $_GET['callback'];
sleep(5); //为了测试jquery ajax跨域工作原理,这里设置5秒后再返回数据
echo $callback."($result)"; //这句是关键
总结:
1、原理与上例一毛一样,也是jsonp跨域,只不过这里是亲自用原生js操作,比用jQuery更加容易理解。
2、很明显,服务器返回的也是一样对testfunc()
函数的调用,并传入了js对象作为参数,所以你可以在testfunc()
函数里做你要做的事情。
3、服务器怎么知道客户端有个叫testfunc()
的函数呢?因为我们在请求链接上告诉它了。
二、使用代理(proxy)方式跨域
1、使用后端语言代理
html代码
<!DOCTYPE HTML>
<html>
<head>
<title>使用“代理”的方式跨域</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
<style>
#screen{word-wrap:break-word;word-break:break-all;}
</style>
</head>
<body>
<div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
<div style="text-align:center;"><input id="getData" type="button" value="获取数据" /></div>
</body>
</html>
对应的js代码
$(document).ready(function(){
$('#getData').click(function(){
var username = 'Bruce';
var mobile = '13838383388';
$.ajax({
type:'post',
url:'proxy_agent.php',
data:{
'username':username,
'mobile':mobile
},
dataType:'json', //注意:因为是通过本域代理来获取,这里不要再写jsonp了
success:function(data){
var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
+'<br>手机号:'+ data.mobile
+'<br>年龄:'+ data.age
+'<br>性别:'+ (data.gender==1 ? '男' : '女')
+'<br>头像:'+ '<img src="'+data.avatar+'">';
+'<br></div>';
$('#screen').append(str);
}
});
});
});
代理php代码:proxy_agent.php
<?php
//proxy_agent.php
echo file_get_contents('http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo3.php');
服务器端php代码:cross_domain_demo3.php
<?php
//假设以下数组是从数据库中查询出来的数据
$userinfo = [
'id'=>2345,
'username'=>'Bruce',
'mobile'=>'13838383388',
'age'=>18,
'gender'=>1,
'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
];
echo json_encode($userinfo);
总结:
1、这是一种间接的跨域方式,ajax无法直接请求另一个域的服务器,那就不直接请求,改成请求本域的后端的接口,由该接口对应的代码去跨域请求服务器(对服务器端代码来说,发起一个跨域请求太简单了,如PHP的话,可以用file_get_contents
方法或者curl去请求),那么这个本域的后端接口,我们称它为『代理』,因为是它代替ajax去获取数据,然后再返回给ajax。
2、直接使用nginx代理
我们知道nginx一大特点是可以设置反向代理,所以对于跨域api,比如前端想请求内网后端同事电脑上的api就可以暂时用nginx代理方式,让ajax请求一个自己定义的本域的url,然后在nginx里把这个url代理到局域网内后端同事的电脑上,这样就可以实现『跨域』了。
三、使用CORS跨域
1、简单请求
html代码
<!DOCTYPE HTML>
<html>
<head>
<title>CORS跨域一:简单请求</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
<style>
#screen{word-wrap:break-word;word-break:break-all;}
</style>
</head>
<body>
<div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
<div style="text-align:center;">
<input id="getData" type="button" value="获取数据" />
</div>
</body>
</html>
对应的js代码
$(document).ready(function(){
$('#getData').click(function(){
var username = 'Bruce';
var mobile = '13838383388';
$.ajax({
type:'get',
url:'http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo4.php',
data:{
'username':username,
'mobile':mobile
},
dataType:'json',
success:function(data){
var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
+'<br>手机号:'+ data.mobile
+'<br>年龄:'+ data.age
+'<br>性别:'+ (data.gender==1 ? '男' : '女')
+'<br>头像:'+ '<img src="'+data.avatar+'">';
+'<br></div>';
$('#screen').append(str);
}
});
});
});
后端php代码:cross_domain_demo4.php
代码与代理跨域方式的后端代码几乎一样,唯一不同的是,该方式在返回数据前,使用header('Access-Control-Allow-Origin:*');
发送了header给浏览器,告诉浏览器,我允许任何人访问我,你就不要再拦截我返回的数据了。
<?php
//允许所有服务器上的文件使用ajax请求该文件
//通过启用CORS(跨域资源共享,Cross-Origin Resource Sharing)来允许所有域名使用ajax请求该文件
header('Access-Control-Allow-Origin:*');
//只允许域名为www.baidu.com的服务器上的文件使用ajax请求该文件
//header('Access-Control-Allow-Origin:https://www.xiebruce.top');
//假设以下数组是从数据库中查询出来的数据
$userinfo = [
'id'=>2345,
'username'=>'Bruce',
'mobile'=>'13838383388',
'age'=>18,
'gender'=>1,
'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
];
echo json_encode($userinfo);
总结:
这种方式,是真正的从源头上允许Ajax跨域,前面的是jsonp和代理方式。
2、非简单请求
html代码
<!DOCTYPE HTML>
<html>
<head>
<title>CORS跨域二:非简单请求</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
<style>
#screen{word-wrap:break-word;word-break:break-all;}
</style>
</head>
<body>
<div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
<div style="text-align:center;">
<input id="getData" type="button" value="获取数据" />
</div>
</body>
</html>
对应的js代码
$(document).ready(function(){
$('#getData').click(function(){
var username = 'Bruce';
var mobile = '13838383388';
$.ajax({
type:'post',
url:'http://xiebruce.top:8080/cross_domain_demo/cross_domain_demo4.php',
beforeSend:function(xhr){
xhr.setRequestHeader("custom-header", "this is a custom header");
},
data:{
'username':username,
'mobile':mobile
},
dataType:'json',
success:function(data){
var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
+'<br>手机号:'+ data.mobile
+'<br>年龄:'+ data.age
+'<br>性别:'+ (data.gender==1 ? '男' : '女')
+'<br>头像:'+ '<img src="'+data.avatar+'">';
+'<br></div>';
$('#screen').append(str);
}
});
});
});
非简单请求会发起两次请求,第一次请求的请求方法是OPTIONS
方法,目的是『探路』,通过这次请求,客户端可以知道服务器是否允许本次请求的请求方法(REQUEST METHOD)、请求头以及请求域,如果都允许,那么客户端(即浏览器)才会发出第二次请求,第二次请求是真正请求数据的请求。由于发起两次请求,相当于请求量多了一倍,会给服务器造成较大压力,一般情况下不建议使用非简单请求。
后端php代码:
<?php
/**
* Created by PhpStorm.
* User: bruce
* Date: 10/01/2018
* Time: 16:15
* corss_domain_demo4.php
*/
//允许所有服务器上的文件使用ajax请求该文件
//通过启用CORS(跨域资源共享,Cross-Origin Resource Sharing)来允许所有域名使用ajax请求该文件
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:PUT,POST,DELETE,GET');
header('Access-Control-Allow-Headers:custom-header');
//只允许域名为http://www.xiebruce.top的服务器上的文件使用ajax请求该文件
//header('Access-Control-Allow-Origin:http://www.xiebruce.top');
//假设以下数组是从数据库中查询出来的数据
$userinfo = [
'id'=>2345,
'username'=>'Bruce',
'mobile'=>'13838383388',
'age'=>18,
'gender'=>1,
'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
];
echo json_encode($userinfo);
响应header必须与客户器端对应,否则客户端不会发起获取数据的请求
四、使用iframe+postMessage跨域
如果iframe里请求的是非本域url,因为同源策略,用下边这种方法是无法获取iframe里返回的数据的:
$('#_iframe').on('load', function (e){
var responseData = this.contentDocument.body.textContent || this.contentWindow.document.body.textContent;
console.log(responseData);
})
利用iframe+postMessage跨域
html代码
<!DOCTYPE HTML>
<html>
<head>
<title>利用iframe+postMessage跨域</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
<style>
#screen{word-wrap:break-word;word-break:break-all;}
</style>
</head>
<body>
<div id="screen" style="width:500px;height:300px;border:1px solid #ccc;margin:0 auto;overflow: auto"></div>
<div style="text-align:center;">
<input id="getData" type="button" value="获取数据" />
</div>
</body>
</html>
对应的js代码
$(document).ready(function(){
var iframeId = '_iframe';
$('#getData').on('click', function (){
//动态创建iframe
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.id = '_iframe';
iframe.src = 'https://www.xiebruce.top:8080/cross_domain_demo/cross_domain_demo5.php?username=Bruce&mobile=13838383388';
$('body').append(iframe);
//iframe内容加载完成时触发onload事件
$('#'+iframeId).on('load', function (){
//当iframe加载完成后(此时iframe中其实已经是http://xiebruce.top:8080页面)
//用iframe的window对象中的postMessage方法向iframe发送消息,消息内容无所谓
//目的是为了触发iframe中的onmessage事件,这样子页面才能把数据传出来
//第二个参数接收消息的窗口域名(*任何域名都可以接收)
$('#'+iframeId).get(0).contentWindow.postMessage('aaa','https://www.xiebruce.top:8080');
});
});
//监听onmessage事件,当有其他窗口(这里是iframe)用postMessage方法往本窗口发送消息时,会触发该事件
$(window).on('message', function (e){
var data = eval("("+e.originalEvent.data+")");
var str = '<div style="border:1px solid #ccc;margin-bottom: 10px;">用户名:'+data.username
+'<br>手机号:'+ data.mobile
+'<br>年龄:'+ data.age
+'<br>性别:'+ (data.gender==1 ? '男' : '女')
+'<br>头像:'+ '<img src="'+data.avatar+'">';
+'<br></div>';
$('#screen').append(str);
$('#'+iframeId).remove();
});
});
后端php代码
<?php
//假设以下数组是从数据库中查询出来的数据
$userinfo = [
'id'=>2345,
'username'=>'Bruce',
'mobile'=>'13838383388',
'age'=>18,
'gender'=>1,
'avatar'=>'http://wx.qlogo.cn/mmopen/2M7faiaIZFeoficAS9h1WjicnJ36XVtvliaMyXEnsvazotoh6Cea3o726N1x3I1A166sl9Cj19scdJ3WYoybW5HVDd4UTcibJaibhR/96',
];
$json = json_encode($userinfo);
$str = <<<EOF
<script>
window.addEventListener('message', function (ev){
//向父窗体发送消息,这里ev.source=window.parent,随便用其中一个即可
ev.source.postMessage('$json', ev.origin);
}, false);
</script>
EOF;
echo $str;
postMessage支持跨域源通信,所以我们可以利用它把iframe页面中的数据传到父窗口,进而使用这些服务器返回数据。
服务器返回的是监听message事件的js代码,当我们访问页面后,页面内的iframe同时也跳转到了指定的地址(该地址即为跨域的地址,比如api的接口,这时其实接口数据已经返回了),因为iframe加载了api的数据,所以父窗体的iframe onload事件被触发,然后往ifame中发送消息,iframe此时虽然处于另一个域,但由于postMessage支持跨域,所以iframe中的onmessage被触发,进而给父窗体发消息并把服务器返回的数据通过消息带出去,父窗体的onmessage事件接收到该消息后就能拿到服务器返回的数据,进而使用这些数据。
五、总结
1、跨域的方法目前我知道的就是这四种方法:jsonp、CORS、proxy、iframe+postMessage
2、最理想的跨域当然是CORS了,jsonp也常用,proxy方式不建议使用,毕竟有两次请求,而iframe+postMessage方式由于我不是很了解,不是特别清楚它与jsonp之间哪个好。