@xudongh
2017-10-08T23:46:34.000000Z
字数 7788
阅读 1722
前端开发
下面是MDN对于ajax的简单解释:
AJAX stands for Asynchronous JavaScript And XML. In a nutshell, it is the use of the XMLHttpRequest object to communicate with servers.
ajax的全称是Asynchronous JavaScript and XML(异步JavaScript和XML)。简而言之,ajax是使用XMLHttpRequset
对象来与服务器进行通讯的。它可以传送并接收多种格式的数据,包括JSON、XML、HTML以及文件等。ajax最吸引人的地方在于其“异步”的特性,着表明着它可以在与服务器通讯时,不需要重载页面。
ajax的概念:
要使用ajax发送一个http请求,需要通过XMLHttpRequest()
构造器创建一个XMLHttpRequest对象实例:
let xhr = new XMLHttpRequset()
然后,现在可以向服务器发送一个HTTP请求:
xhr.open('GET', 'api/example') // 通过GET方式请求数据,
// 请求的数据可以是JSON、HTML、图片等等,
// 这依赖于api/example这个域名下服务器返回的数据类型是什么
xhr.send() //发送数据
XMLHttpRequset的属性:
1.onreadystatechange
XMLHttpRequset的回调函数,监听该xhr对象的变化。也可以使用onload
进行监听。
xhr.onreadystatechange = function() {
// code here...
}
2.readyState
该属性只可读,表示请求的状态,有5个值:
0
:UNSENT(未打开),此时open()
方法还未被调用1
:OPENED(未发送),此时send()
方法还未被调用2
:HEADERS_RECEIVED(已获取响应头),此时send()方法已经被调用, 响应头和响应状态已经返回3
:LOADING(正在下载响应体),响应体下载中,responseText中已经获取了部分数据4
:DONE(请求完成),请求完成3.response
该属性只可读,响应体数据,通过http请求得到的数据在response
属性当中。
其数据类型由responseType
来指定。
如果请求未完成或者失败,该值为null
。
4.responseType
设置该值能够改变响应类型,即告诉服务器你期望的响应格式。其属性值为:
ArrayBuffer
blob
Document
5.responseText
该属性只可读,responseText
也是响应体数据,与response
类似,该属性表示服务器返回的文本数据。后面会详细说这两者的区别。
6.status
该属性只可读,该请求的响应状态码 (例如, 状态码200,表示一个成功的请求)。
7.statusText
该属性只可读, 该请求的响应状态信息,包含一个状态码和原因短语 (例如 "200 OK")。
8.timeout
设置HTTP请求的时限,若超过这个时限,就会自动停止HTTP请求。
xhr.timeout = 3000 // 如果时间超过3S,请求会自动停止
9.ontimeout
与上面timeout
匹配的事件,用于指定当回调函数:
xhr.ontimeout = function() {
alert('请求超时!')
}
以上是常用的XMLHttpRequest对象的属性。一个完整的实例应该如下,此处我们使用formData创建数据,并通过POST方法向服务器提交。
function sendAjax() {
//构造表单数据
var formData = new FormData();
formData.append('username', 'Daniel');
formData.append('id', 123456);
//创建xhr对象
var xhr = new XMLHttpRequest();
//设置xhr请求的超时时间
xhr.timeout = 3000;
//设置响应返回的数据格式
xhr.responseType = "text";
//创建一个 post 请求,采用异步
xhr.open('POST', '/server', true);
//注册相关事件回调处理函数
xhr.onload = function(e) {
if(xhr.readyState === 4 && xhr.status === 200){
alert(this.responseText);
}
};
xhr.ontimeout = function(e) { ... };
xhr.onerror = function(e) { ... };
xhr.upload.onprogress = function(e) { ... };
//发送数据
xhr.send(formData);
}
在发送Ajax请求(实质是一个HTTP请求)时,我们可能需要设置一些请求头部信息,比如content-type
、connection
、cookie
、accept-xxx
等。xhr提供了setRequestHeader
来允许我们修改请求 header。
xhr.setRequestHeader('header', 'value')
注意:
header
大小写不敏感,即可以写成content-type
,也可以写成Content-Type
,甚至写成content-Type
;setRequestHeader
必须在open()
方法之后,send()
方法之前调用,否则会抛错;setRequestHeader
可以调用多次,最终的值不会采用覆盖override的方式,而是采用追加append的方式。如下:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'api/example');
xhr.setRequestHeader('X-Test', 'one');
xhr.setRequestHeader('X-Test', 'two');
// 最终request header中"X-Test"为: one, two
client.send();
xhr提供了2个用来获取响应头部的方法:getAllResponseHeaders
和getResponseHeader
。前者是获取response
中的所有header
字段,后者只是获取某个指定header
字段的值。另外,getResponseHeader(header)
的header参数不区分大小写。
xhr.getAllResponseHeaders() // 获取response中所有header
xhr.getResponseHeader(header) // 获取response中指定header
对于这两个方法,值得注意的是:
Set-Cookie
、Set-Cookie2
这2个字段;response header
字段只限于“simple response header”
和“Access-Control-Expose-Headers”
。上面所指的simple response header
包括的字段为:Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
,Pragma
;
而对于Access-Control-Expose-Headers
,该字段是在跨域请求中出现的,对于同域请求,响应头部是没有这个字段。这个字段中列举的 header 字段就是服务器允许暴露给客户端访问的字段。
因此,getAllResponseHeaders()
只能拿到限制以外(即被视为safe)的header
字段,而不是全部字段;而调用getResponseHeader(header)
方法时,header
参数必须是限制以外的header
字段,否则调用就会报Refused to get unsafe header
的错误。
xhr提供了3个属性来获取请求返回的数据,分别是:xhr.response
、xhr.responseText
、xhr.responseXML
。
xhr.response
xhr.responseType
有关:当responseType
为""或"text"时,值为"";responseType
为其他值时,值为 nullxhr.responseText
responseType
为"text"、""时,xhr对象上才有此属性,此时才能调用xhr.responseText
,否则抛错xhr.responseXML
responseType
为"text"、""、"document"时,xhr对象上才有此属性,此时才能调用xhr.responseXML
,否则抛错xhr默认发的是异步请求,但也支持发同步请求(当然实际开发中应该尽量避免使用)。到底是异步还是同步请求,由xhr.open()
传入的async参数决定。
open(method, url [, async = true [, username = null [, password = null]]])
method
: 请求的方式,如GET/POST/HEADER等,这个参数不区分大小写url
: 请求的地址,可以是相对地址如example.php
,这个相对是相对于当前网页的url路径;也可以是绝对地址如http://www.example.com/example.php
async
: 默认值为true,即为异步请求,若async=false,则为同步请求值得注意的是,当xhr为同步请求时,有如下限制:
- xhr.timeout
必须为0
- xhr.withCredentials
必须为 false
- xhr.responseType
必须为""(注意置为"text"也不允许)
若上面任何一个限制不满足,都会抛错,而对于异步请求,则没有这些参数设置上的限制。
我们要避免使用sync同步请求的原因是,由于我们无法设置请求超时时间(xhr.timeout
为0,即不限时)。在不限制超时的情况下,有可能同步请求一直处于pending
状态,服务端迟迟不返回响应,这样整个页面就会一直阻塞,无法响应用户的其他交互。
在上传或者下载比较大的文件时,实时显示当前的上传、下载进度是很普遍的产品需求。
我们可以通过onprogress
事件来实时显示进度,默认情况下这个事件每50ms触发一次。需要注意的是,上传过程和下载过程触发的是不同对象的onprogress
事件:
xhr.upload
对象的 onprogress
事件xhr
对象的onprogress
事件
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
function updateProgress(event) {
if (event.lengthComputable) {
var completedPercent = event.loaded / event.total;
}
}
上面的代码中,event.total
是需要传输的总字节,event.loaded
是已经传输的字节。如果event.lengthComputable
不为真,则event.total
等于0。
与progress
事件相关的,还有其他六个事件,可以分别指定回调函数,下面包括progress
事件:
onloadstart
事件:传输开始onprogress
事件:传输进度load
事件:传输成功完成abort
事件:传输被用户取消error
事件:传输中出现错误loadstart
事件:传输开始loadEnd
事件:传输结束,但是不知道成功还是失败上面已经总结了七个xhr事件,这些事件均属于XMLHttpRequestEventTarget
。
因此,再包括我们前面提过的一个事件,可见XMLHttpRequest
一共有八个相关事件:7个XMLHttpRequestEventTarget
事件+1个独有的onreadystatechange
事件。
事件 | 触发条件 |
---|---|
onreadystatechange |
每当xhr.readyState 改变时触发;但xhr.readyState 由非0值变为0时不触发。 |
onloadstart |
调用xhr.send() 方法后立即触发,若xhr.send() 未被调用则不会触发此事件。 |
onprogress |
xhr.upload.onprogress 在上传阶段(即xhr.send() 之后,xhr.readystate =2之前)触发,每50ms触发一次;xhr.onprogress 在下载阶段(即xhr.readystate =3时)触发,每50ms触发一次。 |
onload |
当请求成功完成时触发,此时xhr.readystate =4 |
onloadend |
当请求结束(包括请求成功和请求失败)时触发 |
onabort |
当调用xhr.abort() 后触发,传输被用户取消 |
ontimeout |
xhr.timeout 不等于0,由请求开始即onloadstart 开始算起,当到达xhr.timeout 所设置时间请求还未结束即onloadend ,则触发此事件。 |
onerror |
在请求过程中,若发生Network error 则会触发此事件(若发生Network error 时,上传还没有结束,则会先触发xhr.upload.onerror ,再触发xhr.onerror ;若发生Network error 时,上传已经结束,则只会触发xhr.onerror )。注意,只有发生了网络层级别的异常才会触发此事件,对于应用层级别的异常,如响应返回的xhr.statusCode 是4xx时,并不属于Network error ,所以不会触发onerror 事件,而是会触发onload 事件。 |
xhr.onreadystatechange
(之后每次readyState
变化时,都会触发一次)xhr.onloadstart
xhr.upload.onloadstart
xhr.upload.onprogress
xhr.upload.onload
xhr.upload.onloadend
xhr.onprogress
xhr.onload
xhr.onloadend
abort
/timeout
/error
异常的处理在请求的过程中,有可能发生 abort
/timeout
/error
这3种异常。那么一旦发生这些异常,xhr后续会进行哪些处理呢?后续处理如下:
1. 一旦发生abort
或timeout
或error
异常,先立即中止当前请求
2. 将readystate
置为4,并触发 xhr.onreadystatechange
事件
3. 如果上传阶段还没有结束,则依次触发以下事件:
xhr.upload.onprogress
xhr.upload.[onabort或ontimeout或onerror]
xhr.upload.onloadend
4. 触发xhr.onprogress
事件
5. 触发xhr.[onabort或ontimeout或onerror]
事件
触发xhr.onloadend
事件
从上面介绍的事件中,可以知道若xhr请求成功,就会触发xhr.onreadystatechange和xhr.onload两个事件。 那么我们到底要将成功回调注册在哪个事件中呢?实际上两个事件都可以,如下:
xhr.onreadystatechange = function () {
//如果请求成功
if(xhr.readyState == 4 && xhr.status == 200){
//do successCallback
}
}
xhr.onload = function () {
//如果请求成功
if(xhr.status == 200){
//do successCallback
}
}
上面的示例代码是很常见的写法:先判断http状态码是否是200,如果是,则认为请求是成功的,接着执行成功回调。这样的判断是有坑的,比如当返回的http状态码不是200,而是201时,请求虽然也是成功的,但并没有执行成功回调逻辑。所以更靠谱的判断方法应该是:当http状态码为2xx或304时才认为成功。
xhr.onload = function () {
//如果请求成功
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
//do successCallback
}
}