Ajax的全称就是Asynchronous JavaScript + XML,它的本意是为了可以在不刷新页面的情况下异步更新数据。
而实现这一功能的核心就是XMLHttpRequest(XHR),它能够获取新数据,并通过DOM操作将新数据插入到页面当中。
虽然它的名称中包含有XML,但其实Ajax通信与数据格式没有关系,Ajax可以返回HTML、XML、json、jsonp、text、script等数据类型。
Ajax的一个基本流程
在IE7版本之后,以及Firefox、Opera、Chrome、Safari浏览器中,我们可以直接使用XMLHttpRequest构造函数来创建最新的XML对象。
var xhr = new XMLHttpRequest();
在使用XML对象的第一步是使用open方法,open接受3个参数:
要发送请求的类型(”get”、”post”等)、请求的地址以及是否异步发送请求的布尔值。
xhr.open("get","example.php",false)
其中的第二个参数必须是在同一个域中使用相同端口和协议的地址,否则会引发安全错误。
在这之后调用xhr.send()方法,如果是POST请求的话,send里就需要放入要传的信息
在收到服务器的响应之后,JavaScript会自动填充XHR对象的属性,常用到的属性有以下几个:
- responseText:响应的文本
- status:响应的HTTP状态
- statusText:HTTP状态的说明
首先应该做的是检查status属性,如果是200的话,那么就说明本次响应已经成功了,或者是304的话,说明本地浏览器有缓存,可以直接调用。
同时我们一般是使用异步发送请求,所以我们还得检测XHR对象的readyState属性,该属性表示请求/响应过程的当前阶段。
- 0:尚未调用open()方法
- 1:启动open()方法,但还没启动send()
- 2:调用send(),但还未收到响应
- 3:已经接收到部分信息
- 4:已经收到全部信息
每一个阶段的改变都会触发一次onreadystatechange事件。
所以一个基本的Ajax流程的例子如下所示:
var xhr = new XMLHttpRequest();
xhr.open("get","example.php",false);
xhr.send();
xhr.onreadystatechange = function (){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300)) || xhr.status == 304){
alert(xhr.responseText)
}else {
alert(xhr.status)
}
}
}
XMLHttpRequest2级
XHR1级已经把Ajax的实现细节都给描述出来了,在此基础上,XHR2级对整个XHR进行了升级进化。
主要有以下几个方面:
- FormData:
用于序列化表单以及创建与表单格式相同的数据,使用new创建并且直接通过send函数发送。
创建时可以直接传入键值对
var data = new FormData();
data.append("name", "Tuncan")
或者预先传入表单元素
var data = new FromData(document.forms[0])
- 超时设定
XHR对象新增了一个timeout属性,表示请求在多少时间之后便会终止,在给定时间到达之后,还没有接到响应便会触发timeout事件并调用omtimeout事件处理程序。
xhr.timeout = 1000;
xhr.ontimeout = function (){
alert("Request did not return in a second")
}
- overrideMimeType
用于重写XHR的MIME类型,该类型决定XHR对象如何处理返回的响应,例如如果服务器返回的MIME类型是text/plain,但里面的文档类型是XML,根据MIME类型XHR的responseXML是null,所以需要overrideMimeType在send调用前重写MIME类型为XML才能处理。
var xhr = new XMLHttpRequest();
xhr.open("get","text.php",true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
XMLHttpRequest的进度事件
在刚刚使用XHR实现Ajax的流程当中,可以发现整个响应会分为不同的阶段,在W3C里为其定义为了6个进度事件:
loadstart:接收到响应数据的第一个字节时触发
progress:接受响应期间不断地触发
error:请求错误时触发
abort:调用abort()方法终止连接时触发
load:接收到完整响应时触发
loadend:通信完成或者触发error、abort或load事件时触发
每一个请求都是以loadstart开始,经历一个或多个progress,然后接到error、abort或是load中的一个,最后以loadened结束。
其中的load事件和progress事件有一些细节需要注意:
- load事件
Firefox曾致力于简化异步交互模型,所以引入load事件来代替readystatechange事件,响应接受完毕之后才会触发load事件,因此也没有必要去检查readyState属性了。
xhr.onload = function(){
if ((xhr.status >= 200 && xhr.status < 300)) || xhr.status == 304){
alert(xhr.responseText)
}else {
alert(xhr.status)
}
}
- progress事件
progress事件用以表示当前接收数据的进度,它会被周期性地触发,每次触发都会返回一个event对象,event对象的target属性是XHR对象
同时它还含有三个属性:
lengthComputable:进度信息是否可用
position:表示已接收的字节数
totalSize:表示响应头部的预期字节数
xhr.onprogress = function (event){
var status = document.getElementById("status")
if (event.lengthComputable){
status.innerHTML = "Received" + event.positon + "of" + event.totalSize
}
}
需要注意的是要在xhr.open()之前定义好该事件
跨域资源共享——突破XHR的限制
因为在默认情况下,页面通过XHR只能访问跟它处在同一个域中的资源,这是来源于跨域安全策略。
但是在实际额开发过车个当中,我们一定会遇上需要进行跨域请求的地方,那么这时候就需要用上CORS了
CORS(Cross-Orign Resource Sharing,跨域资源共享),其基本思想就是使用自定义的Http头部实现浏览器与服务器之间的沟通,从而确定请求是否成功。
例如,对于一个请求,在头部里加入Origin
Origin: http://www.nczonline.net
如果服务器认为该请求可以接受,就在Access-Control-Allow-Origin头部中回发相同的源信息
Access-Control-Allow-Origin: http://www.nczonline.net
这样就完成了一次CORS。
跨域资源共享在非IE浏览器中,如Firefox3.5+,Safari 4+,Chrome,iOS版Safari和Android的WebKit中,都通过XMLHttpRequest实现了对CORS的原生支持。处于安全考虑,加上了如下的限制:
- 不能使用自定义头部
- 不能发送和接受Cookie
- getAllResponseHeaders()总会返回空字符串
在IE浏览器当中,主要是通过引入XDR对象,创建时通过new XDomainRequest穿件即可
该对象与XHR最重要的不同之处就在于不能使用Cookie,只支持GET和POST请求,以及只支持异步请求
如果开发一定要使用Cookie的话,可以将withCredentials属性设置为true,如果服务器接受该请求,就会使用Access-Control-Allow-Credentials: true来响应
最后如果想要实现跨浏览器的CORS时,通过检查XHR中是否带有withCredentials属性,再结合检查XDomainRequest是否存在,就可以兼顾所有浏览器了。
function createCORSRequest (method,url){
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr){
xhr.open(method,url,true);
}else if ( typeof XDomainRequest != "undefined"){
xhr = new XDomainRequest();
xhr.open(method,url)
}else {
xhr = null
}
return xhr
}
其它的跨域方式
虽然说现在CORS已经无处不在,但在其发明出来之前,要想实现跨域请求还可以通过DOM中一些自带的功能,毕竟这样可以省去修改服务器代码的时间。
- 图像Ping
通过<img>标签可以直接加载其它网站上的图片,而不用担心跨不跨域的问题,它自己带有onload和onerror事件,可以用于处理响应成功和失败事件。
var img = new Image()
img.src = "https://pic4.zhimg.com/v2-de96b4afbe2140ace8c268bb60112283_b.jpg"
但是这种方法有两个缺点,一是只能使用GET请求,二是无法访问服务器的文本
- JSONP
JSONP是JSON with padding的简写,它是通过动态调用<script>标签来使用的。
script.src = url
一个JSONP分为两个部分,一是回调函数,一般回调函数的名字是要写在请求地址中的,二是从服务器传回来的JSON数据。
callback({ "name" : "Nicholas" })
而一个JSONP请求长这样:
http://freegeoip.net/json/?callback=handleReponse
一个正常的JSONP请求长这样:
function handleResponse(response){
alert(response.city)
}
var script = document.createElement("script")
script.src = "http://freegeoip.net/json/?callback=handleReponse"
- Comet
Comet与Ajax是彼此相反的,Comet是从服务器向页面推送数据的技术。
有两种方式可以实现,长轮询和HTTP流
长轮询的意思页面发送一个请求,与服务器建立好链接并保持打开的状态,当服务器有数据可发送时才发送。
HTTP流的意思是只发送一次请求,建立好连接之后服务器会周期性地发送消息。在页面的实现上可以通过检测readyState,因为周期性发送会使readyState周期性地变为3,每次变为3的时候将新数据接到已接收的数据上。
- SSE
管理Comet的时候很容易出错,同时为了简化这一技术,Comet就被赋予了两个接口,SSE和Web Socket。
SSE指的是服务器发送事件,用于建立与服务器的单向连接,使用时需要先创建一个EventScore对象,它拥有3个事件。
open:建立连接时触发
message:从服务器接收到新事件时触发
error:无法建立连接时触发
在message事件中通过event.data可以获取到响应数据
source.onmessage = function (event){
return event.data;
}
对于Comet的事件流,SSE通过将Http的响应MIME类型设置为text/event-stream,处理如下:
data: bar
data: foo
该响应事件收到的消息值为barnfoo。另外可以通过为事件设置id来进行标记
data:bar
id:1
在连接中断的时候,EventSource对象会把Last-Event-ID放入到Http的头部里,并发往服务器,这样服务器就知道是从哪个事件中断的了。
- Web Sockets
Web Sockets的目标在于为一个单独的持久连接上提供全双工、双向的通信服务。创建Web Sockets之后,会先发送一个Http请求已建立连接,然后再将Http连接升级成Web Socket协议。
也就是说Web Sockets使用了自定义的协议,由http://变成了ws://,https://变成了wss://
这个协议的好处就在于可以支持客户端与服务器之间比较少量的通信,而不用承担Http协议那种字节级别的开销。
使用Web Socket的时候,首先先建立一个WebSocket对象并传入要连接的URL
var socket = new WebSocket(url)
建立对象之后,浏览器马上就会尝试创建新的连接,WebSocket拥有与XHR类似的readyState属性,不过不太一样,而且它没有readystateChange事件,但是其是用其它事件来对应不同状态。
分别是:
open:在成功连接时触发
error:在连接发生错误时触发
close:在连接关闭时触发
而其中只有close事件是有额外的信息,即:wasClean、code和reason,其中wasClean是一个布尔值,表示连接是否成功关闭;code表示服务器返回的状态码;reason表示服务器发回的信息。
socket.onclose = function (event){
alert("Was Clean?"+ event.wasClean "Code = " + event.code + "Reason = " + event.reason
}
因为其是双向通信,所以发送与接收数据的方法如下:
//发送
socket.send("Hello World")
//接收
socket.onmessage = function (event){
var data = event.data
}