前言:这是笔者学习之后自己的理解与整理。如果有错误或者疑问的地方,请大家指正,我会持续更新!
AJAX
是asynchronousjavascript and XML
的简写,就是异步的javascript
和XML
。这一技术能够向服务器请求额外的数据而无须刷新整个页面,会带来更好的用户体验。虽然名字中包含XML
,但是ajax
通信与数据格式无关。
创建对象
因为XMLHTTPRequest()
是一个构造函数,所以需要实例化一个XMLHttpRequset
对象。下面是创建XHR
对象的兼容写法;
如果要建立多个不同的请求,就要实例化多个不同的XMLHttpRequset
对象;
var xhr;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
发送请求
要想把请求发送到服务器,我们就需要使用open()
方法和send()
方法。
open()
open()
方法需要三个参数:xhr.open("GET","test.json",true);
第一个参数定义发送请求所使用的方法(GET
还是POST
),不区分大小写,但通常使用大写字母,记得带引号。
GET
用于常规请求,它适用于当URL
完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存的情况下。
然而,在以下情况中,请使用POST
请求:
- 无法使用缓存文件(更新服务器上的文件或数据库)
- 向服务器发送大量数据(
POST
没有数据量限制) - 发送包含未知字符的用户输入时,
POST
比GET
更稳定也更可靠
第二个参数规定服务器端脚本的URL
(该文件可以是任何类型的文件,比如.txt
和.xml
,或者服务器脚本文件,比如.asp
和.php
(在传回响应之前,能够在服务器上执行任务)。
第三个参数规定是否异步发送请求的布尔值,如果不填写,默认为true
,表示异步发送。如果接受的是同步响应,则需要将open()
方法的第三个参数设置为false
,那么send()
方法将阻塞直到请求完成。客户端javascript
是单线程的,当send()
方法阻塞时,它通常会导致整个浏览器界面冻结。如果连接的服务器响应慢,那么用户的浏览器将冻结,所以应该避免使用同步。
send()
send()
方法接收一个参数,即要作为请求主体发送的数据。调用send()
方法后,请求被分派到服务器。
如果是GET
方法,send()方法无参数,或参数为null
;如果是POST
方法,send()
方法的参数为要发送的数据。
xhr.open("GET","test.json",false);
xhr.send(null);
GET
GET
用于常规请求,它适用于当URL
完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存的情况下。
【数据发送】
使用GET
方式发送请求时,数据被追加到open()
方法中URL
的末尾,可以直接看到,存在安全隐患。
数据以问号开始,名和值之间用等号链接,名值对之间用和号&
分隔。使用GET
方式发送的数据常常被称为查询字符串。
【编码】
由于URL
无法识别特殊字符,所以如果数据中包含特殊字符(如中文),则需要进行编码,编码的方式有很多种,其中encodeURIComponent()
函数可把字符串作为URI
组件进行编码。该方法主要对;/?:@&=+$,#
等这些用于分隔URI
组件的字符以及中文进行编码。由于此方法对:/
都进行了编码,所以不能用它来对网址进行编码,而适合对URI
中的参数进行编码
在GET
请求中,为了避免缓存的影响,可以向URL
末尾添加一个随机数或时间戳。
var url = 'test.php' +'?name=' + encodeURIComponent("你好");
xhr.open('GET',url+'&'+Number(new Date()),true);
xhr.send(null);
POST
POST
请求通常用于向服务器发送应该被保存的数据。
POST
方法常用于HTML
表单。它在请求主体中包含额外数据且这些数据常存储到服务器上的数据库中。
在open()
方法第一个参数的位置传入POST
,就可以初始化一个POST
请求。
【设置请求头】
默认情况下,服务器对POST
请求和提交表单的请求并不会一视同仁。因此,服务器端必须有程序来读取发送过来的原始数据,并从中解析出有用的部分。不过,可以使用XHR
来模仿表单提交:首先将content-Type
头部信息设置为application/x-www-form-urlencoded
,也就是表单提交时的内容类型;
使用setRequestHeader()
方法可以设置自定义的请求头部信息。这个方法接受两个参数:头部字段的名称头部字段的值。要成功发送请求头部信息,必须在调用open()
方法之后且调用send()
方法之前调用setRequestHeader()
方法。
在项目中,又是需要验证用户登录,可以设置请求头验证。机制就是:在用户首次登录成功之后,服务器发送token
到客户端,客户端存入cookie
。用户做任何请求操作时,在ajax
的请求头里带上token
,用以server-end
做登录状态验证。
【发送主体】
发送POST
请求的第三步就是向send()
方法中传入某些数据,这一点和GET
请求不一样。由于XHR
最初的设计主要是为了处理XML
,因此可以在此传入XML DOM
文档,传入的文档经序列化之后将作为请求主体被提交到服务器。当然,也可以在此传入任何想发送到服务器的字符串。
接下来要以适当的格式创建一个字符串,并使用send()
方法发送。
POST
数据的格式与GET
数据的格式相同,名和值之间用等号链接,名值对之间用和号&
分隔。
【编码】
由于使用POST
方式传递数据时,需要设置请求头"content-type",这一步骤已经能够自动对特殊字符(如中文)进行编码,所以就不再需要使用encodeURIComponent()
方法了。
POST
请求主要用于数据提交,相同URL
的重复POST
请求从服务器得到的响应可能不同,所以不应该缓存使用POST
方法的请求。
GET
对所发送信息的数量有限制,一般在2000
个字符。与GET
请求相比,POST
请求消耗的资源会更多一些。从性能角度来看,以发送相同的数据计,GET
请求的速度最多可POST
请求的两倍。
xhr.open('POST',url,true);
//设置请求头
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
//拼接数据
var strData = 'name="abc"&num=123';
//发送请求
xhr.send(strData);
接收响应
一个完整的HTTP
响应由状态码、响应头集合和响应主体组成。
在收到响应后,这些都可以通过XMLHttpRequset
对象的属性和方法使用,主要有以下4
个属性:
responseText
作为响应主体被返回的文本(文本格式)responseXML
如果响应的内容类型是"text/xml"
或"application/xml"
,这个属性中将保存着响应数据的XML - DOM
文档(document
格式)status
HTTP
状态码(数字形式)statusText
HTTP
状态说明(文本格式)
在接收到响应后,第一步是检查status
属性,以确定响应已经成功返回。一般来说,可以将HTTP
状态码为200
作为响应成功的标志。此时,responseText
属性的内容已经就绪,而且在内容类型正确的情况下,responseXML
也可以访问了。此外,状态码为304
表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本;当然,也意味着响应是有效的。
无论内容类型是什么,响应主体的内容都会保存到responseText
属性中,而对于非XML
数据而言,responseXML
属性的值将为null
;
if((xhr.status >=200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert('请求失败,响应代码为:' + xhr.status);
}
异步响应和同步响应
如果不设置open()
方法的第三个参数(默认为true
,即异步响应)。
如果接收的是异步响应,这就需要检测XMLHttpRequset
对象的readyState
属性,该属性表示请求/响应过程的当前活动阶段。这个属性可取的值如下:0(UNSENT) 未初始化 还没调用open()
状态码 | 状态 | 描述 |
---|---|---|
1 | (OPEND) 启动 | 已经调用open() ,但还没调用 send() |
2 | (HEADERS_RECEIVED) 发送 | 己经调用 send() 方法,且接收到头信息。 |
3 | (LOADING) 正在接收 | 已经接收到部分响应主体信息。 |
4 | (DONE) 完成 | 已经接收到全部响应数据,而且已经可以在客户端使用了。 |
理论上,只要readyState
属性值由一个值变成另一个值,都会触发一次readystatechange
事件。可以利用这个事件来检测每次状态变化后readyState
的值。通常,我们对readyState
值为4的阶段感兴趣,因为这时所有数据都已就绪。
必须在调用open()
之前指定onreadystatechange
事件处理程序才能确保跨浏览器兼容性,否则将无法接收readyState
属性为0
和1
的情况。
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status == 200){
alert(xhr.responseText);
}
}
}
如果将open()
方法的第三个参数设置为false
,接收的就是同步响应,那么send()
方法将阻塞直到请求完成。一旦send()
返回,仅需要检查XHR
对象的status
和responseText
属性即可。
应该避免使用同步请求。客户端javascript
是单线程的,当send()
方法阻塞时,它通常会导致整个浏览器UI
冻结。如果连接的服务器响应慢,那么用户的浏览器将冻结,用户体验非常不好。
进度事件
一般地,使用readystatechange
事件探测HTTP
请求的完成。XHR2
规范草案定义了进度事件Progress Events
规范,XMLHttpRequest
对象在请求的不同阶段触发不同类型的事件,所以它不再需要检査readyState
属性。
有以下6
个进度事件:
loadstart
: 在接收到响应数据的第一个字节时触发progress
: 在接收响应期间持续不断地触error
: 在请求发生错误时触发abort
: 在因为调用abort()
方法而终止连接时触发load
: 在接收到完整的响应数据时触发loadend
: 在通信完成或者触发error、abort或load
事件后触发timeout
: 超时发生时触发
每个请求都从触发loadstart
事件开始,接下来,通常每隔50毫秒左右触发一次progress
事件,然后触发load、error、abort或timeout
事件中的一个,最后以触发loadend
事件结束
对于任何具体请求,浏览器将只会触发load、abort、timeout和error
事件中的一个。XHR2
规范草案指出一旦这些事件中的一个发生后,浏览器应该触发loadend
事件。
load
响应接收完毕后将触发load
事件,因此也就没有必要去检查readyState
属性了。但一个完成的请求不一定是成功的请求,例如,onload
事件的处理程序应该检查XMLHttpRequest
对象的status
状态码来确定收到的是“200OK”而不是“404 Not Found”
的HTTP
响应
progress
progress
事件会在浏览器接收新数据期间周期性地触发。而onprogress
事件处理程序会接收到一个event
对象,其target
属性是XHR
对象,但包含着三个额外的属性:lengthComputable、loaded 和 total
。其中,lengthComputable
是一个表示进度信息是否可用的布尔值,loaded
表示已经接收的字节数,total
表示根据Content-Length
响应头部确定的预期字节数。有了这些信息,就可以为用户创建一个进度指示器了。
upload上传进度
除了为监控HTTP
响应的加载定义的这些有用的事件外,XHR2
也给出了用于监控HTTP
请求上传的事件。在实现这些特性的浏览器中,XMLHttpRequest
对象将有upload
属性。upload
属性值是一个对象,它定义了addEventListener()
方法和整个progress
事件集合,比如onprogress
和onload
。但upload
对象没有定义onreadystatechange
属性,upload
仅能触发新的事件类型。
<input type="file" name="file1" id="file1" style="display:none">
<button id="btn">上传文件</button>
<div id="pro"></div>
<div id="result"></div>
<script>
btn.onclick = function(){
file1.click();
pro.innerHTML = result.innerHTML = '';
}
file1.onchange = function(){
//创建xhr对象
var xhr = new XMLHttpRequest();
var data = file1.files[0];
//上传事件
xhr.upload.onprogress = function(e){
e = e || event;
if (e.lengthComputable){
pro.innerHTML = "上传进度为:" + e.loaded + " of " + e.total + " bytes" + ';百分比为:' + e.loaded/e.total;
}
}
xhr.onload = function(e){
var data = xhr.responseText;
e = e || event;
if(xhr.status == 200){
result.innerHTML = data;
}
};
//发送请求
xhr.open('post','pp.php',true);
xhr.setRequestHeader("content-type",data.type);
xhr.send(data);
}
</script>
超时、中止、错误事件
HTTP
请求无法完成有3种情况。如果请求超时,会触发timeout
事件。如果请求中止,会触发abort
事件。最后,像太多重定向这样的网络错误会阻止请求完成,但这些情况发生时会触发error
事件。
可以通过调用XMLHttpRequest
对象的abort()
方法来取消正在进行的HTTP
请求。调用abort()
的主要原因是完成取消或超时请求消耗的时间太长或当响应变得无关时。
XHR
对象的timeout
属性等于一个整数,表示多少毫秒后,如果请求仍然没有得到结果,就会自动终止。该属性默认等于0
,表示没有时间限制。如果请求超时,将触发ontimeout
事件。
var xhr = new XMLHttpRequest();
btn.onclick = function(){
xhr.abort();
}
xhr.onabort = function(){
console.log("请求已终止");
}
xhr.ontimeout = function(){
console.log('请求超时');
}
xhr.timeout = 3000;
xhr.onerror = function(){
console.log("请求报错");
}
xhr.onloadend = function(){
console.log("请求结束");
}
封装
ajax({
url: "./TestXHR.aspx", //请求地址
type: "POST", //请求方式
data: {
name: "super",
age: 20
}, //请求参数
dataType: "json",
success: function (response, xml) {
// 此处放成功后执行的代码
},
fail: function (status) {
// 此处放失败后执行的代码
}
});
function ajax(options) {
options = options || {};
options.type = (options.type || "GET").toUpperCase();
options.dataType = options.dataType || "json";
var params = formatParams(options.data);
//创建 - 非IE6 - 第一步
if (window.XMLHttpRequest) {
var xhr = new XMLHttpRequest();
} else { //IE6及其以下版本浏览器
var xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
//接收 - 第三步
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
var status = xhr.status;
if (status >= 200 && status < 300) {
options.success && options.success(xhr.responseText, xhr.responseXML);
} else {
options.fail && options.fail(status);
}
}
}
//连接 和 发送 - 第二步
if (options.type == "GET") {
xhr.open("GET", options.url + "?" + params, true);
xhr.send(null);
} else if (options.type == "POST") {
xhr.open("POST", options.url, true);
//设置表单提交时的内容类型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
}
//格式化参数
function formatParams(data) {
var arr = [];
for (var name in data) {
arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]));
}
arr.push(("v=" + Math.random()).replace(".", ""));
return arr.join("&");
}