- 首先,说明一下ajax产生的背景:在传统的B/S结构中,存在这样的问题:
一个小模块中的数据向服务器请求响应到数据区的刷新往往是需要刷新整个页面的。这个方法 弊端是:I/O网络吞吐量过大,网页加载慢,并且消耗内存及CPU.但是,采用iframe进行局部整体刷新,又会暴露出iframe的缺点:外面的window和iframe的window不是同一个对象,也就是说它俩不在同一个DOM树中,这样外面内容和里面内容的交互就变得繁琐了。
因此,才有了ajax。
- 正文
一、ajax的概述
ajax的全称是‘Asynchronous JavaScript And XML',代表一种格式,例如:json,HTML,XML,text,jsonp.
ajax的核心对象是XMLHttpRequest对象。
1、原生的ajax
格式:
// 简单的ajax请求
var xhr = new XMLHttpRequest();// 创建
xhr.onreadystatechange = function() {// onreadystatechange 不是检测方法,而是状态改变后更新的状态
if (xhr.status == 200) {
/*
普及一下常用的状态码
200:ok,服务器成功返回数据
400:Bad Request,语法错误
401:请求需要认证
404:not found,找不到页面
500:服务器遇到意外错误
503:服务器正在维护或者过载无法完成请求
其他百度去
*/
if (xhr.readyState == 4) {
/*
xhr.readyState 是 ajax 状态
普及一下这个4是什么意思:
0:创建服务
1:打开服务
2:发送服务
3:服务器响应
4:加载成功
*/
var data = JSON.parse(xhr.responseText);// responseText 是返回的文本或对象
}
} else {
// 如果不是正常返回
console.log("数据返回失败!状态码" + xhr.status + "状态信息:" + xhr.statusText)
// xhr.statusText是浏览器的错误信息,因为跨浏览器的时候,可能不太一致,不建议直接使用它
}
}
xhr.open("post", "list.json?rand="+new Date(), true);// 打开,最后一个 bool 代表是否异步,这一步仅仅只配置了 ajax 的基本信息,而并没有对服务器请求
// rand=new Date()是为了让每一次请求 url 都不同,用来区分缓存
xhr.setRequestHeader("Content-Type","application/www-x-form-urlencoded");
xhr.send(null);// 向服务器发送请求
2、jQuery的ajax封装了原生的ajax,如下:
$.ajax({
type:"get",
dataType: "json",
url: url,
async: true,
success: function(){},
error: function(){}
})
二、ajax的跨域
1、跨域是指什么?
是指:浏览器不能执行其他网站的脚本。这是由浏览器的同源策略导致的,这是浏览器对JavaScript的安全限制。
2、同源指的是什么?
指:同域名、同协议、同端口。不满足三个时,发生跨域。举例:
http://www.123.com/index.html 调用 http://www.123.com/server.PHP (非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。
浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。
你可以理解为两个域名之间不能跨过域名来发送请求或者请求数据,否则就是不安全的,这种不安全也就是CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
3、原生ajax跨域请求会发生报错,得到如下结论:
ajax本身是允许跨域的,但是由于浏览器的同源策略的机制,限制了XMLHttprequest请求,导致无法跨域,所以ajax在浏览器无法跨域是个伪命题,而ajax不是无法跨域的主谋。
4、解决跨域问题的五种方法
1)、JSONP:
js实现:
//创建一个script元素
var Scr = document.reateElement('script');
//声明类型
Scr.type='text/javascript';
//添加src属性,引入跨域访问的url
Scr.src=url;
//在页面中添加新创建的script元素
document.getElementsByTagName('body')[0].appendChild(Scr)
jQuery中实现:
$.ajax({
url: 'http://192.168.1.114/yii/demos/test.js', //不同的域
type: 'GET', // jsonp模式只有GET是合法的
data: {
'action': 'aaron'
}, // 预传参的数组
dataType: 'jsonp', // 数据类型
jsonp: 'backfunc', // 指定回调函数名,与服务器端接收的一致,并回传回来
})
客户端发送一个请求,规定一个可执行的函数名(这里就是jQuery做了封装的处理,自动帮你生成回调函数并把数据取出来供success属性方法来调用,不是传递的一个回调句柄),服务端接受了这个backfunc函数名,然后把数据通过实参的形式发送出去。
.但是要注意JSONP只支持GET请求,不支持POST请求。
2)、代理:
例如www.123.com/index.html需要调用www.456.com/server.php,可以写一个接口www.123.com/server.php,由这个接口在后端去调用www.456.com/server.php并拿到返回值,然后再返回给index.html,这就是一个代理的模式。相当于绕过了浏览器端,自然就不存在跨域问题。
3)、PHP端修改header(XHR2方式)
在php接口脚本中加入以下两句即可:
header('Access-Control-Allow-Origin:*');//允许所有来源访问
header('Access-Control-Allow-Method:POST,GET');//允许访问的方式
4)、iframe
所以跨域通信其实很简单,在iframe和主页里都不断地检测hashtag有没有变化,一旦有变化,就做出相应的改变。
setInterval(function() { var hashVal = window.location.hash.substr(1); document.body.style.backgroundColor = hashVal; }, 1000);
这么做的问题就是,需要不断地去检测hashtag是否改变,效率有点低,如果能通过原生的监听来实现,就会更加高效和优雅。这里就涉及到另一个iframe特性:可以设置其他iframe的大小,即使是不同域的。而页面的resize事件是可以监听的,所以就有了下面这个模型。
主页面先把消息附加到hashtag,然后改变一个隐藏的(或者页面外的)iframe的size。这个iframe会监听resize事件,同时捕获到hashtag。捕获到hashtag后(也就是所需的数据),再对hashtag做进一步的处理。处理完后把数据传到主页内的一个iframe,或者直接操作该iframe。这样就比较优雅地完成了跨域操作。
5)、 通过 window.name 实现跨域
有三个页面:
- a.com/app.html:应用页面。
- a.com/proxy.html:代理文件,一般是一个没有任何内容的html文件,需要和应用页面在同一域下。
- b.com/data.html:应用页面需要获取数据的页面,可称为数据页面。
实现起来基本步骤如下:
- 在应用页面(a.com/app.html)中创建一个iframe,把其src指向数据页面(b.com/data.html)。
数据页面会把数据附加到这个iframe的window.name上,data.html代码如下:<script type="text/javascript"> window.name = 'I was there!'; // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右 // 数据格式可以自定义,如json、字符串 </script>
- 在应用页面(a.com/app.html)中监听iframe的onload事件,在此事件中设置这个iframe的src指向本地域的代理文件(代理文件和应用页面在同一域下,所以可以相互通信)。app.html部分代码如下:
<script type="text/javascript"> var state = 0, iframe = document.createElement('iframe'), loadfn = function() { if (state === 1) { var data = iframe.contentWindow.name; // 读取数据 alert(data); //弹出'I was there!' } else if (state === 0) { state = 1; iframe.contentWindow.location = "http://a.com/proxy.html"; // 设置的代理文件 } }; iframe.src = 'http://b.com/data.html'; if (iframe.attachEvent) { iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe); </script>
- 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)。
<script type="text/javascript"> iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); </script>
总结起来即:iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。