一、XMLHttpRequest的发展历程
Ajax
技术的核心是XMLHttpRequest
对象。我们使用XMLHttpRequest
对象来发送一个Ajax
请求。这是由微软首先引入的一个特性,其他浏览器提供商后来都提供了相同的实现。
XMLHttpRequest
已经得到广泛接受,后来W3C
对它进行了标准化,提出了XMLHttpRequest
标准。XMLHttpRequest
标准又分为Level 1
和Level 2
。
并非所有浏览器都完整地实现了XMLHttpRequest 2级
规范,但所有浏览器都实现了它规定的部分内容。
XMLHttpRequest Level 1
主要存在以下缺点:
- 不能发送二进制文件(如图片、视频、音频等),只能发送纯文本数据。
- 在发送和获取数据的过程中,无法实时获取进度信息,只能判断是否完成。
- 受同源策略的限制,不能发送跨域请求。
Level 2
对Level 1
进行了改进,XMLHttpRequest Level 2
中新增了以下功能:
- 在服务端允许的情况下,可以发送跨域请求。
- 支持发送和接收二进制数据。
- 新增
formData
对象,支持发送表单数据。 - 发送和获取数据时,可以获取进度信息。
- 可以设置请求的超时时间。
下面的一行代码就可以创建XMLHttpRequest
对象。
const xhr = new XMLHttpRequest()
兼容性查询:caniuse.com/#search=XML…
二、XMLHttpRequest对象发送请求相关API
请求头相关
Accept
:客户端可以处理的内容类型。比如:Accept: */*
。Accept-Charset
:客户端可以处理的字符集类型。比如:Accept-Charset: utf8
。Accept-Encoding
:客户端可以处理的压缩编码。比如:Accept-Encoding: gzip, deflate, br
。Accept-Language
:客户端当前设置的语言。比如:Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
。Connection
:客服端与服务器之间连接的类型。比如:Connection: keep-alive
。Cookie
:当前页面设置的任何Cookie
。Host
:发出请求页面所在的域。Referer
:表示当前请求页面的来源页面的地址,即当前页面是通过此来源页面里的链接进入的。User-Agent
:客户端的用户代理字符串。一般包含浏览器、浏览器内核和操作系统的版本型号信息。Content-Type
:客户端告诉服务器实际发送的数据类型。比如:Content-Type: application/x-www-form-urlencoded
。
open()方法
open()
方法用于初始化一个请求。
open()
方法接收三个参数:
-
第一个参数
method
:要发送的请求的类型。比如GET
、POST
、PUT
、DELETE
等。 -
第二个参数
url
:请求的URL
。 -
第三个参数
async
:是否异步发送请求的布尔值。true
为异步发送请求。const xhr = new XMLHttpRequest() xhr.open('get', '/userInfo', true)
调用open()
方法并不会真正发送请求,而只是启动一个请求以备发送。
send()方法
send()
方法用于发送HTTP
请求。
send()
方法接收一个参数:
-
第一个参数
data
:作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null
。该参数可以接收字符串、FormData
、Blob
、ArrayBuffer
等类型。const xhr = new XMLHttpRequest() xhr.send(null)
setRequestHeader()方法
setRequestHeader()
方法可以设置自定义的请求头部信息。
setRequestHeader()
方法接收二个参数:
- 第一个参数
header
:头部字段的名称。 - 第二个参数
value
:头部字段的值。
要成功发送请求头部信息,此方法必须在open()
和send()
之间调用。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.setRequestHeader('MyHeader', 'MyValue')
xmlhttp.send()
readyState属性和onreadystatechange事件
readyState
属性表示请求/响应过程的当前活动阶段。这个属性的值如下:
- 0(
UNSENT
)未初始化。尚未调用open()
方法。 - 1(
OPENED
)启动。已经调用open()
方法,但没有调用send()
方法。 - 2(
HEADERS_RECEIVED
)发送。已经调用send()
方法,但尚未接收到响应。 - 3(
LOADING
)接收。已经接收到部分响应数据。 - 4(
DONE
)完成。已经接收到全部响应数据。
只要readyState
属性的值发生变化,都会触发一次onreadystatechange
事件。利用这个事件来检测每次状态变化后readyState
的值。一般情况下只对readyState
值为4
的阶段做处理,这时所有数据都已经就绪。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.onreadystatechange = function () {
if(xhr.readyState !== 4) {
return
}
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.responseText)
}
}
xhr.send(null)
timeout属性和ontimeout事件
timeout
属性表示请求在等待响应多少毫秒之后就终止。如果在规定的时间内浏览器还没有接收到响应,就会触发ontimeout
事件处理程序。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
//将超时设置为3秒钟
xhr.timeout = 3000
// 请求超时后请求自动终止,会调用 ontimeout 事件处理程序
xhr.ontimeout = function(){
console.log('请求超时了')
}
xhr.send(null)
overrideMimeType()方法
overrideMimeType()
方法能够重写服务器返回的MIME
类型,从而让浏览器进行不一样的处理。
假如服务器返回的数据类型是text/xml
,由于种种原因浏览器解析不成功报错,这时就拿不到数据。为了拿到原始数据,我们可以把MIME
类型改成text/plain
,这样浏览器就不会去自动解析,从而我们就可以拿到原始文本了。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.overrideMimeType('text/plain')
xhr.send(null)
responseType属性
responseType
属性是一个字符串,表示服务器返回数据的类型。使用xhr.response
属性来接收。
这个属性是可写的,可以在调用open()
方法之后,send()
方法之前设置这个属性的值,告诉服务器返回指定类型的数据。如果responseType
设为空字符串,等同于默认值text
。
responseType
属性可以设置的格式类型如下:
responseType
属性的值
response
属性的数据类型
说明
""
String
字符串
默认值,等同于text
(在不设置responseType
时)
"text"
String
字符串
服务器返回文本数据
"document"
Document
对象
希望返回XML
格式数据时使用
"json"
javaScript
对象
IE10/IE11
不支持
"blob"
Blob
对象
服务器返回二进制对象
"arrayBuffer"
ArrayBuffer
对象
服务器返回二进制数组
当将responseType
设置为一个特定的类型时,你需要确保服务器所返回的类型和你所设置的返回值类型是兼容的。那么如果两者类型不兼容,服务器返回的数据就会变成null
,即使服务器返回了数据。
给一个同步请求设置responseType
会抛出一个InvalidAccessError
的异常。
// 获取一张图片代码示例
const xhr = new XMLHttpRequest()
xhr.open('get', '/server/image.png', true)
xhr.responseType = 'blob'
xhr.onload = function(e) {
if (xhr.status >= 200 && xhr.status < 300) {
const blob = this.response
// ...
}
}
xhr.send(null)
withCredentials属性
withCredentials
属性是一个布尔值,表示跨域请求时是否协带凭据信息(cookie
、HTTP
认证及客户端SSL
证明等)。默认为false
。
如果需要跨域Ajax
请求发送Cookie
,需要withCredentials
属性设为true
。如果在同域下配置xhr.withCredentials
,无论配置true
还是false
,效果都会相同。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.withCredentials = true
xhr.send(null)
当配置了withCredentials
为true
时,必须在后端增加response
头信息Access-Control-Allow-Origin
,且必须指定域名,而不能指定为*
。还要添加Access-Control-Allow-Credentials
这个头信息为true。
response.addHeader("Access-Control-Allow-Origin", "http://example.com")
response.addHeader("Access-Control-Allow-Credentials", "true")
abort()方法和onabort事件
在接收到响应之前调用abort()
方法用来取消异步请求。当一个请求被终止后,它的readyState
属性将被置为0
。在终止请求之后,还应该对XMLHttpRequeat
对象进行解引用操作。
当调用abort()
后,会触发onabort
事件。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xmlhttp.onabort = function () {
console.log('请求被中止')
}
xmlhttp.send()
// 将会调用我们上面定义的 onabort 回调函数
xmlhttp.abort()
GET请求
将查询字符串参数追加到URL
的末尾,将信息发送给服务器。
GET
参数的编码方式是无法人为干涉的,这导致了不同浏览器有不同的编码方式,因此最稳妥的方案是人工预编码,人工解码,从而禁止浏览器编码的干涉。
const xhr = new XMLHttpRequest()
// 使用encodeURIComponent()进行编码
const tempParam = encodeURIComponent('age')
const tempValue = encodeURIComponent('20')
xhr.open('get', '/server?tempParam=tempValue&money=100', true)
POST请求
POST
请求把数据作为请求的主体(请求的body
)提交。下面是四种常见的POST
请求提交数据方式。
application/x-www-form-urlencoded
浏览器的原生<form>
表单,如果不设置enctype
属性,那么最终就会以application/x-www-form-urlencoded
方式提交数据。
multipart/form-data
表单上传文件时,必须让<form>
表单的enctype
等于multipart/form-data
。
application/json
当发送Ajax
请求时,把application/json
作为请求头,用来告诉服务端消息主体是序列化后的JSON
字符串。
text/xml
使用HTTP
作为传输协议,XML
作为编码方式的远程调用规范。
使用XMLHttpRequest模拟表单提交
将Content-Type
头部信息设置为application/x-www-form-urlencoded
。可以使用XMLHttpRequest
对象来模仿表单提交。
const xhr = new XMLHttpRequest()
xhr.open('post', '/server', true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
const form = document.getElementById('myForm')
// serialize()为表单序列化方法
xhr.send(serialize(form))
也可以使用XMLHttpRequest level 2
的FormData
来序列化表单数据。
const xhr = new XMLHttpRequest()
xhr.open('post', '/server', true)
const form = document.getElementById('myForm')
const formData = new FormData(form)
formData.append("id", "123456")
xhr.send(formData)
使用FormData
不必明确地在XMLHttpRequest
对象上设置请求头部。XMLHttpRequest
对象能够识别传入的数据类型是FormData
的实例,并配置适当的头部信息。
XMLHttpRequest进度事件相关API
onloadstart
在XMLHttpRequest
对象开始传送数据时被触发,也就是调用send()
方法(HTTP
请求发出)的时候。
xhr.onloadstart = function () {
console.log('开始发出请求...')
}
onprogress
在接收响应期间持续不断地触发。
onprogress
事件处理程序会接收到一个event
对象,它的target
属性是XMLHttpRequest
对象,并且event
包含着三个额外的属性:loaded
、total
和lengthComputable
。
event.loaded
:已传输的数据量(已经接收的字节数)。event.total
:总共的数据量(根据Content-Length
响应头部确定的预期字节数)。event.lengthComputable
:进度信息是否可用的布尔值。
有了这些信息,就可以创建一个Ajax
请求进度条了。
const xhr = new XMLHttpRequest()
xhr.onprogress = function (event) {
if (!event.lengthComputable) {
return console.log('无法计算进展')
}
const percentComplete = event.loaded / event.total * 100
console.log(`进度百分比:${percentComplete}%`)
}
xhr.open('post', '/server', true)
xhr.send(null)
onerror
在请求发生错误时触发。只有发生了网络层级别的异常才会触发此事件,对于应用层级别的异常,比如响应返回的statusCode
是4xx
时,并不属于NetWork Error
,所以不会触发onerror
事件,而是会触发onload
事件。
xhr.onerror = function(e) {
console.log('数据接收出错')
}
onabort
调用abort()
方法而终止请求时触发。
onload
当请求成功,接收到完整的响应数据时触发。
可以使用onload
事件可以用来替代readystatechange
事件。因为响应接收完毕后将触发onload
事件,因此也就没有必要去检查readyState
属性了。只要浏览器接收到服务器的响应,不管其状态如何,都会触发load
事件。
const xhr = new XMLHttpRequest()
xhr.onload = function onload() {
console.log('数据接收完毕')
if(xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.responseText)
}
}
xhr.open('post', '/server', true)
xhr.send(formData)
为确保正常执行,必须在调用open()
方法之前添加onprogress
事件处理程序。
onloadend
在请求结束(包括请求成功和请求失败),或者触发error
、abort
或load
事件后触发。
xhr.onloadend = function(e) {
console.log('请求结束,状态未知')
}
每个请求都从触发
loadstart
事件开始,接下来是一或多个progress
事件,然后触发error
、abort
或load
事件中的一个,最后以触发loadend
事件结束。
upload属性
XMLHttpRequest
不仅可以发送请求,还可以发送文件,就是Ajax
文件上传。
发送文件以后,通过XMLHttpRequest.upload
属性可以得到一个XMLHttpRequestUpload
对象。通过这个对象,可以得知上传的进展。实现方案就是监听这个对象的各种事件:onloadstart
、onprogress
、onabort
、onerror
、onload
、ontimeout
、onloadend
。
当文件上传时,对upload
属性指定progress
事件的监听函数,可获得上传的进度。
const xhr = new XMLHttpRequest()
if (xhr.upload) {
xhr.upload.onprogress = function progress(e) {
if (e.total > 0) {
e.percent = e.loaded / e.total * 100
}
}
}
三、XMLHttpRequest对象接收响应相关API
在接收到响应后,第一步是检查status
属性。以确定响应已经成功返回。将HTTP
状态代码为200
作为成功的标志。状态代码为304
表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本,也被认为是有效的。
响应头相关
Content-Type
:服务器告诉客户端响应内容的类型和采用字符编码。比如:Content-Type: text/html; charset=utf-8
。Content-Length
:服务器告诉客户端响应实体的大小。比如:Content-Length: 8368
。Content-Encoding
:服务器告诉客户端返回的的压缩编码格式。比如:Content-Encoding: gzip, deflate, br
。
status属性
status
属性返回一个整数,表示服务器回应的HTTP
状态码。如果服务器没有返回状态码,那么这个属性默认是200
。请求发出之前,该属性为0
。该属性只读。
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 处理服务器的返回数据
}
}
statusText属性
statusText
属性返回一个字符串,表示服务器发送的状态说明。比如OK
和Not Found
。在请求发送之前,该属性的值是空字符串。如果服务器没有返回状态提示,该属性的值默认为OK
。该属性为只读属性。
要通过检测status
属性来决定下一步的操作,不要依赖statusText
,因为statusText
在跨浏览器使用时不太可靠。
response属性
response
属性表示服务器返回的数据。它可以是任何数据类型,比如字符串、对象、二进制对象等等,具体的类型由XMLHttpRequest.responseType
属性决定。该属性只读。
如果本次请求没有成功或者数据不完整,该属性等于null
。
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log(xhr.response)
}
}
responseText属性
responseText
属性返回从服务器接收到的字符串,该属性为只读。
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 处理服务器的返回数据
console.log(xhr.responseText)
}
}
responseXML属性
如果响应的内容类型是"text/xml"
或"application/xml"
,这个属性中将保存包含着响应数据的HTML
或XML
文档对象。该属性是只读属性。
无论内容类型是什么,响应主体的内容都会保存到responseText属性中。而对于非XML数据而言,responseXML属性的值将为null。
responseURL属性
responseURL
属性是字符串,表示发送数据的服务器的网址。如果URL
为空则返回空字符串。如果URL
有锚点,则位于URL#
后面的内容会被删除。如果服务器端发生跳转,这个属性返回最后实际返回数据的网址。
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://example.com/test', true)
xhr.onload = function () {
// 返回 http://example.com/test
console.log(xhr.responseURL)
}
xhr.send(null)
getResponseHeader()方法
getResponseHeader()
方法返回HTTP
头信息指定字段的值,如果还没有收到服务器的响应或者指定字段不存在,返回null
。该方法的参数不区分大小写。
const xhr = new XMLHttpRequest()
xhr.onload = function onload() {
console.log(xhr.getResponseHeader('Content-Type'))
}
xhr.open('post', '/server', true)
xhr.send(null)
如果有多个字段同名,它们的值会被连接为一个字符串,每个字段之间使用逗号+空格分隔。
getAllResponseHeaders()方法
getAllResponseHeaders()
方法返回一个字符串,表示服务器发来的所有HTTP
头信息。格式为字符串,每个头信息之间使用CRLF
分隔(回车+换行),如果没有收到服务器回应,该属性为null
。如果发生网络错误,该属性为空字符串。
const xhr = new XMLHttpRequest()
xhr.onload = function onload() {
const responseHeaders = 'getAllResponseHeaders' in xhr ? xhr.getResponseHeaders() : null
}
xhr.open('post', '/server', true)
xhr.send(null)
上面代码用于获取服务器返回的所有头信息。返回值可能是下面这样的字符串。
content-encoding: gzip\r\n
content-length: 2020\r\n
content-type: text/html; charset=utf-8\r\n
需要对这个字符串进行处理才能正确使用。
const str = 'date: Fri, 08 Dec 2017 21:04:30 GMT\r\n'
+ 'content-encoding: gzip\r\n'
function trim(str) {
return str.replace(/^\s*/, '').replace(/\s*$/, '')
}
function parseHeaders(headers) {
if (!headers) {
return {}
}
const parsed = {}
let key, val, i
const arr = headers.split(/[\r\n]+/)
arr.forEach((line) => {
i = line.indexOf(':')
key = trim(line.substr(0, i)).toLowerCase()
val = trim(line.substr(i + 1))
if (key) {
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val
}
})
return parsed
}
//{date: "Fri, 08 Dec 2017 21:04:30 GMT", content-encoding: "gzip"}
console.log(parseHeaders(str))
来自河北石家庄的用户 2天前
很棒,很棒
来自浙江杭州的用户 3天前
大神
来自北京昌平的用户 5天前
good
来自浙江杭州的用户 5天前
可以的,老铁,点个赞
来自四川成都的用户 8天前
感谢分享,希望作者继续出文章,多交流分享。
来自山西太原的用户 13天前
文章挺好,先赞再看,点赞这么少,真的没人喜欢这么用嘛
来自北京朝阳的用户 18天前
开始学习
来自北京朝阳的用户 19天前
点赞,前排摸大佬沾点技术
来自天津南开的用户 23天前
感谢。
来自福建福州的用户 25天前
非常感谢,眼界开阔了
来自山西太原的用户 26天前
学习了, 总结的很细很棒, 赞