从AJAX诞生那天起,XMLHttprequest对象不能跨域请求的问题就一直存在,这似乎是一个很经典的问题了。这是由于同源策略所导致。这里引入下同源策略的百科:
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
围绕Ajax的两大问题就是 一、Ajax以何种格式来交换数据;二、就是跨域问题。交换数据的格式就是json(JavaScript Object Notation) 一种轻量级的数据交换格式。所以这里主要来研究如何解决Ajax的跨域问题。
首先看看jQuery中的Ajax的调用用法:
$.ajax({
url : "",
type : "get/post",
dataType : "json/jsonp",
data : {
jian : ,
zhi : ,
dui :
},
success : function(data){
//请求成功后
}
});
在调用jQuery中的Ajax解决跨域问题时,只需要在dataType中选择是json还是jsonp即可。那么问题来了,jsonp是什么呢?
贴一下百科:
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的
JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。
首先,jsonp与json虽然只差一个"p"的区别,但是绝对不是一个类型的东西。json是数据格式,而jsonp是一种跨域请求方式,两者可谓是天差地别。
根据上面百科的解释,我们可以了解到jsonp实现跨域的方法就是通过创建script标签。利用script标签的src属性来获取到“任意的JavaScript”。顺便一提,src 属性是用来规定外部脚本文件的 URL。话不多说,我们来实现一下:
<script>
//成功后的回调函数
var callBack = function(data){
//请求成功后执行
};
//提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
var url = "";
// 创建script标签,设置其属性
var script = document.createElement('script');
script.setAttribute('src', url);
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);
</script>
如此一来,我们就最简单的实现了jsonp跨域。整理一下思路:1、创建script标签;2、拼接script标签的src;3、将script标签插入到head标签中;4、回调函数
可以看到,思路并不复杂。
但是,在一开始引用的jQuery中的Ajax里,我们只需要在dataType那里设置是json还是jsonp就能实现,这是怎么做到的呢?
先上代码,我自己封装的与jQuery中的Ajax近似用法的对象:
var my = {
createXHR : function(){
if(window.XMLHttpRequest){
//IE7+、Firefox、Opera、Chrome 和Safari
return new XMLHttpRequest();
}else if(window.ActiveXObject){
//IE6 及以下
return new ActiveXObject('Microsoft.XMLHTTP');
}else{
throw new Error('浏览器不支持XHR对象!');
}
},
ajax : function(obj){
if(obj.dataType === 'json'){
var xhr = this.createXHR(); //创建XHR
//通过使用JS随机字符串解决IE浏览器第二次默认获取缓存的问题
obj.url = obj.url + '?rand=' + Math.random();
obj.data = this.formatParams(obj.data); //通过params()将名值对转换成字符串
if(obj.method === 'get'){
obj.url += obj.url.indexOf('?') == -1 ? '?' + obj.data : '&' + obj.data;
}
if(obj.async === true){ //true表示异步,false表示同步
//使用异步调用的时候,需要触发readystatechange 事件
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) { //判断对象的状态是否交互完成
callback(); //回调
}
};
}
//在使用XHR对象时,必须先调用open()方法,
//它接受三个参数:请求类型(get、post)、请求的URL和表示是否异步。
xhr.open(obj.method, obj.url, obj.async);
if (obj.method === 'post'){
//post方式需要自己设置http的请求头,来模仿表单提交。
//放在open方法之后,send方法之前。
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(obj.data); //post方式将数据放在send()方法里
} else {
xhr.send(null); //get方式则填null
}
if(obj.async === false){ //同步
callback();
}
var callback = function(){
//判断http的交互是否成功,200表示成功
if(xhr.status == 200){
obj.success(xhr.responseText); //回调传递参数
} else {
alert('获取数据错误!错误代号:' + xhr.status + ',错误信息:' + xhr.statusText);
}
};
} else if (obj.dataType === "jsonp"){
var oHead = document.getElementsByTagName("head")[0];
var oScript = document.createElement("script");
var callbackName = 'callback' + new Date().getTime();
var params = this.formatParams(obj.data) + '&callback=' + callbackName; //按时间戳拼接字符串
//拼接好src
oScript.src = obj.url.split("?") + '?' + params;
//插入script标签
oHead.insertBefore(oScript, oHead.firstChild);
//jsonp的回调函数
window[callbackName] = function(json){
var callback = obj.success;
callback(json);
oHead.removeChild(oScript);
};
}
},
formatParams : function(data){
var arr = [];
for(var i in data){
//特殊字符传参产生的问题可以使用encodeURIComponent()进行编码处理
arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(data[i]));
}
return arr.join('&');
}
};
调用时的用法:
my.ajax({
method : 'get',
url : '',
data : {
jian : '',
zhi : '',
dui : ''
},
dataType : 'json/jsonp',
async : 'true/false',
success : function(data){
//.......
}
});
这里可以看到与jQuery的Ajax相差无几。详细的细节在注释里写的比较清楚,这里讲一下大体的思路。
由于json与jsonp是完全不同的东西,所以在ajax方法里首先判断dataType是json还是jsonp。如果是json,则创建xhr对象,在上面代码中我单独把创建xhr写成了一个方法。由于不管是json还是jsonp都需要拼接url,所以把这个也单独写成了formatParams方法,方便调用。这里值得一提的是jsonp只支持get请求而不支持post请求。
做一个小实验,如果在需要跨域的时候用了在dataType那里写了json会怎样:
可以看到浏览器会报错,这里提一下报错的那个 “原因:CORS头缺少‘Access-Control-Allow-Origin’”,其意思是没有关闭同源策略,不允许跨域请求。所以是可以通过设置关掉同源策略,但是那样会很不安全。
解决跨域的问题的方法还有很多,可以根据实际的需求来选择选择不同的策略。