处于安全考虑,浏览器采用同源策略限制不同协议、域名、端口之间的ajax请求,但是当我们有需求这样做的时候该怎么办呢?经过前辈们的尝试发现script标签不受同源策略的限制,因此制定了一套方案用来解决跨域的问题.如下,我们在site1.dev中向site2.dev发起一个ajax跨域请求:
<!DOCTYPE html>
<html>
<head>
<title>jsonp</title>
</head>
<body>
<script type="text/javascript">
var xhr = new XMLHttpRequest ();
xhr.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
console.log (this.responseText);
}
}
xhr.open('GET', 'http://site2.dev/test.php');
xhr.send ();
</script>
</body>
</html>
可以看到,我们什么数据也没有取到,而且还报了一个错:
Access to XMLHttpRequest at 'http://site2.dev/test.php' from origin 'http://site1.dev' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
也就是跨域的问题,下面我们用jsonp做一个案例,解决这个问题.总的来说,分为以下几个步骤:
1.客户端声明一个全局函数,用于接收并处理服务端数据.
2.新建script标签,链接到跨域的接口,并把全局函数的名字传递给服务端.
3.服务端设置Content-Type为text/javascript,然后返回一段js代码,在代码中调用全局函数,把数据传递进去.另外,服务端可以判断是否有函数名称传递过来,如果没有的话,按照非跨域请求正常处理即可.
下面用代码实现一下以上逻辑,客户端:
<!DOCTYPE html>
<html>
<head>
<title>jsonp</title>
</head>
<body>
<script type="text/javascript">
//把跨域请求封装为一个函数
function crossRequest (url, callback, datas = {}) {
//生成随机函数名
var funcName = 'jsonp' + Date.now ().toString (36) + Math.random ().toString (36).substr (3, 5);
//拼接url和参数,把函数名称也传递到服务端
var tempArr = [];
for (var key in datas) {
tempArr.push (key + '=' + datas[key]);
}
url = url + '?' + tempArr.join('&') + '&callback=' + funcName;
//创建script标签,先不渲染
var scrNode = document.createElement('script');
scrNode.setAttribute ('type', 'text/javascript');
scrNode.setAttribute ('src', url);
//声明全局函数,处理服务端返回的数据
window[funcName] = function (datas) {
//回调处理返回数据
callback (datas);
//到此,整个流程结束,释放内存空间:1.删除script标签 2.释放全局函数
document.body.removeChild (scrNode);
delete window[funcName];
}
//所有准备工作已经做好,可以开始渲染标签,发起请求
document.body.appendChild (scrNode);
}
//测试
crossRequest ('http:\/\/site2.dev\/test.php', datas => {
console.log (datas);
});
</script>
</body>
</html>
php服务端:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
//处理数据
$datas = "{'name': '周星星', 'age': 18}";
//判断参数中是否有callback名称,有的话为跨域请求;没有的话则为普通请求
if (isset($_GET['callback'])) {
//设置响应类型为js
header('Content-Type: text/javascript');
//返回一段js代码,代码中调用callback,并把数据传递过去
$func_name = $_GET['callback'];
echo $_GET['callback'] . '(' . $datas . ')';
} else {
//设置相应类型为json
header ('Content-Type: application/json');
echo $datas;
}
}
?>
打印结果:
然后在site2中非跨域请求这个接口:
同样好使.