XMLHttpRequest的属性和方法
XMLHttpRequest的属性和方法
Ajax技术的出现是为了解决发送服务器请求额外数据而不刷新整张页面的问题,从而实现更好的用户体验。把Ajax技术推到历史舞台上的关键技术是XMLHttpRequest对象。最早由微软发明,然后被其他浏览器所借鉴。
在Ajax技术出现之前,这种浏览器与服务器的通信早在1998年就通过不同方式实现了。最初JavaScript对服务器的请求可以通过中介(如Java小程序或Flash影片)来发送。后来XHR对象提供了原生的浏览器通信能力,减少了实现这个目的的工作量。
由于XMLHttpRequest对象本身非常难用,所以以往我们看到很多库实现了封装来帮助我们更好地使用它。为了了解更多XMLHttpRequest的细节,今天我们就来了解一下XMLHttpRequest的属性、方法和事件,帮助大家了解Ajax的底层能力。
declare var XMLHttpRequest: {
prototype: XMLHttpRequest;
new(): XMLHttpRequest;
readonly DONE: number;
readonly HEADERS_RECEIVED: number;
readonly LOADING: number;
readonly OPENED: number;
readonly UNSENT: number;
}
属性
- readyState
- status
- statusText
- responseType
- response
- responseText
- responseXML
- responseURL
- timeout
- upload
- withCredentials
readyState属性(只读)
该属性表示当前所处状态。
值 | 状态 | 说明 |
---|---|---|
0 | UNSENT | 未初始化状态,XMLHttpRequest已被创建,但尚未调用open方法 |
1 | OPENED | 发送状态,open方法已被触发,在这个状态中,可以通过setRequestHeader 方法来设置请求的头部,可以通过send 方法来发起请求 |
2 | HEADERS_RECEIVED | 发送状态,send方法已被触发,响应头也已经被接收 |
3 | LOADING | 正在接收状态,响应体部分正在被接收 |
4 | DONE | 接受完全状态,数据传输已经彻底完成或者失败 |
var xhr = new XMLHttpRequest();
console.log('UNSENT', xhr.readyState); // readyState 为 0
xhr.onprogress = function () {
console.log('LOADING', xhr.readyState); // readyState 为 3
};
xhr.onload = function () {
console.log('DONE', xhr.readyState); // readyState 为 4
};
xhr.open('GET', '/api', true);
console.log('OPENED', xhr.readyState); // readyState 为 1
// 在open方法和send方法之间执行setRequestHeader,以保证Header设置有效
xhr.setRequestHeader('x-token', 'test_token');
xhr.send(null);
status属性(只读)
返回XMLHttpRequest请求中由服务器返回的数字状态码。
注意:
- 在请求完成前,状态码始终为0。
- 如果XMLHttpRequest出错,浏览器返回的状态码也为0。
- 如果请求正常执行,状态码与标准的HTTP状态码一致。
/**
* 输出如下:
*
* UNSENT(未发送)0
* OPENED(已打开)0
* LOADING(载入中)200
* DONE(完成)200
*/
var xhr = new XMLHttpRequest();
console.log('UNSENT', xhr.status); // status 为 0
xhr.onprogress = function () {
console.log('LOADING', xhr.status); // status 为 200
};
xhr.onload = function () {
console.log('DONE', xhr.status); // status 为 200
};
xhr.open('GET', '/server', true);
console.log('OPENED', xhr.status); // status 为 0
xhr.send(null);
statusText属性(只读)
返回XMLHttpRequest请求中由服务器返回的字符串状态信息,这则信息中也包含了响应的数字状态码。不同于使用一个数字来指示的状态码,这个属性包含了返回状态对应的文本信息,例如“OK”或者“Not Found”。
注意:
- 如果请求的状态
readyState
的值为UNSENT
或者OPENED
,则这个属性的值将会是一个空字符串。 - 如果服务器未明确指定一个状态文本信息,则
statusText
的值将会被默认赋值为”OK”。
responseType属性
该属性是一个枚举字符串值,用于指定响应中包含的数据类型。它允许作者更改响应类型。
可能得值:
- "":空的responseType字符串与默认类型”text”相同。
- “arraybuffer”:response是一个包含二进制数据的JavaScript ArrayBuffer。
- “blob”:response是一个包含二进制数据的Blob对象。
- “document”:response是一个HTML Document或XML XMLDocument,根据接收到的数据的MIME类型而定。
- “json”:response是通过将接收到的数据内容解析为JSON而创建的JavaScript对象。
- “text”:response是DOMString对象的文本。
response属性(只读)
当请求体响应时,该属性会接收到请求体返回的数据。具体类型取决于responseType的值。
可能的响应类型有ArrayBuffer
、Blob
、Document
、DOMString
等
responseText属性(只读)
当请求体响应时,该属性会接收到请求体返回的数据。
responseXML属性(只读)
当请求体响应时,该属性返回一个包含请求检索的HTML或XML的Document。(根据Content-Type
为text/html
或者application/xml
判断)
注意:
- 如果请求未成功,尚未发送,或者检索的数据无法解析为XML或者HTML,则为null。
- 如果服务器没有明确指出
Content-Type
头,可以使用overrideMimeType
方法强制XMLHttpRequest
解析为XML。
responseURL属性(只读)
当请求体响应时,返回序列化URL。
注意:
- 如果URL为空,则返回空字符串。
- 如果URL有锚点,则位于
URL#
后面的内容会被删除。 - 如果URL有重定向,
responseURL
的值会是经过多次重定向后的最终URL。
timeout属性
代表着一个请求在被自动终止前所消耗的毫秒数。默认为0,意味着没有超时。
注意:
- 超时并不应该用在一个全局文档环境中的同步XMLHttpRequest请求中,否则将会抛出一个
InvalidAccessError
类型的错误。 - 当超时发生时,timeout事件将被触发。
var xhr = new XMLHttpRequest();
xhr.onload = function () {
// 请求完成。在此进行处理。
};
xhr.ontimeout = function (e) {
// XMLHttpRequest 超时。在此做某事。
};
xhr.open('GET', '/server', true);
xhr.timeout = 2000; // 超时时间,单位是毫秒
xhr.send(null);
upload属性(只读)
返回一个XMLHttpRequestUpload对象,用来表示上传的进度。这个对象时不透明的,但是作为一个XMLHttpRequestEventTarget,可以通过对其绑定事件来追踪它的进度。
可以被绑定在upload对象上的事件监听器如下:
事件 | 相应属性的信息类型 |
---|---|
onloadstart | 获取开始 |
onprogress | 数据传输进行中 |
onabort | 获取操作终止 |
onerror | 获取失败 |
onload | 获取成功 |
ontimeout | 获取操作在用户规定的时间内未完成 |
onloadend | 获取完成(无论成功与否) |
interface XMLHttpRequestEventTarget extends EventTarget {
onabort: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onerror: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onload: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onloadend: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onloadstart: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onprogress: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
ontimeout: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
addEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestEventTarget, ev: XMLHttpRequestEventTargetEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestEventTarget, ev: XMLHttpRequestEventTargetEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
interface XMLHttpRequestUpload extends XMLHttpRequestEventTarget {
addEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestUpload, ev: XMLHttpRequestEventTargetEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestUpload, ev: XMLHttpRequestEventTargetEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
withCredentials属性
该属性是一个布尔类型,它指示了是否该使用类似Cookies、Authorization Headers(头部授权)或者TLS客户端证书这类资格证书来创建一个跨站点访问控制(cross-siteAccess-Control)请求。
注意:
- 在同一站点下使用
withCredentials
属性是无效的。 - 这个指示会被用做响应中Cookies被忽视的标识。默认是false。
- 不同域下的XMLHttpRequest响应,无论其
Access-Control-
Header设置什么值,都无法为它自身站点设置Cookie值(除非请求之前将withCredentials
设置为true
)。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true; // 在发出请求之前设置为 true
xhr.send(null);
方法
open方法
该方法接收三个参数:请求类型(GET、POST等)、请求的URL、是否异步。
需要注意的是,URL是相对于代码所在的页面的,当然你也可以使用绝对URL。另外,调用open方法并不会实际发送请求,只是为发送请求做好准备。
注意:请求默认情况下只能访问同源URL(域名相同、端口相同、协议相同)。如果请求的URL于发送请求的页面在任何方面有所不同,则会抛出安全错误。
注意:由于同步的请求会阻塞JavaScript执行,所以一般情况下,我们不建议使用同步请求的方式。
send方法
该方法接收一个参数:要发送的数据。如果不需要发送任何数据,则必须传null(因为该参数在某些浏览器中是必需的)。调用send方法后,请求就会发送到服务器。
getRequestHeader方法
该方法接收一个参数:Header的名称。返回包含指定响应头文本的字符串。
注意:
- 如果在返回的响应头中有多个一样的名称,那么返回的值就会是用逗号和空格将值分隔的字符串。该方法以UTF编码返回值。
- 搜索的报文名是不区分大小写的。
- 如果连接未完成(响应中不存在报文项或者被W3C限制),则返回null。
setRequestHeader方法
设置HTTP请求头部的方法。
注意:
- 该方法必须在
open
方法和send
方法之间调用。 - 如果多次对同一个请求头赋值,只会生成一个合并了多个值的请求头。
- 如果没有设置Accept属性,那么这个请求头会被默认设置为
*/*
。 - 安全起见,有些请求头的值只能由UserAgent设置:禁止修改的标头和禁止修改的响应标头。
- 自定义的header属性进行跨域请求时,可能会遇到”not allowed by Access-Control-Allow-Headers in preflight response”,你可能需要再你的服务器设置”Access-Control-Allow-Headers”。
getAllResponseHeaders方法
返回所有的响应头,以CRLF分割的字符串。如果没有收到任何响应,则返回null
。
注意:对于复合请求,这个方法返回的当前请求的头部,而不是最初的请求头的头部。
var request = new XMLHttpRequest();
// 为了兼容所有浏览器,该事件处理函数必须放在open方法执行之前
request.onreadystatechange = function() {
if(this.readyState == this.HEADERS_RECEIVED) {
// Get the raw header string
var headers = request.getAllResponseHeaders();
// Convert the header string into an array
// of individual headers
var arr = headers.trim().split(/[\r\n]+/);
// Create a map of header names to values
var headerMap = {};
arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift();
var value = parts.join(': ');
headerMap[header] = value;
});
}
}
request.open("GET", "foo.txt", true);
request.send();
// Headers For Example
// date: Fri, 08 Dec 2017 21:04:30 GMT\r\n
// content-encoding: gzip\r\n
// x-content-type-options: nosniff\r\n
// server: meinheld/0.6.1\r\n
// x-frame-options: DENY\r\n
// content-type: text/html; charset=utf-8\r\n
// connection: keep-alive\r\n
// strict-transport-security: max-age=63072000\r\n
// vary: Cookie, Accept-Encoding\r\n
// content-length: 6502\r\n
// x-xss-protection: 1; mode=block\r\n
overrideMimeType方法
该方法是指一个MIME类型用户替代服务器指定的类型,使服务端响应信息中传输的数据按照指定MIME类型处理。
注意:
- 此方法必须在send方法之前调用方为有效。
- 如果服务器没有指定一个
Content-Type
头,MIME类型默认设置为text/xml
。如果接受的数据不是有效的XML,将会出现“格式不正确”的错误。你能够通过调用overrideMimeType
方法指定各种类型来避免这种情况。
// Interpret the received data as plain text
req = new XMLHttpRequest();
req.overrideMimeType("text/plain");
req.addEventListener("load", callback, false);
req.open("get", url);
req.send();
abort方法
如果请求已被发出,该方法可以终止请求。当一个请求被终止,它的readyState将被置为XMLHttpRequest.UNSENT
(0),并且请求的status
置为0。
注意:如果请求已经达到服务器,服务器相关Controller方法并不会停止执行,只会造成执行结果无法返回,并且服务器会有报错提示。
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://developer.mozilla.org/";
xhr.open(method, url, true);
xhr.send();
if (OH_NOES_WE_NEED_TO_CANCEL_RIGHT_NOW_OR_ELSE) {
xhr.abort();
}
事件
事件响应顺序:loadstart -> progress -> (error/abort/load) -> loadend
interface XMLHttpRequestEventTargetEventMap {
"abort": ProgressEvent<XMLHttpRequestEventTarget>;
"error": ProgressEvent<XMLHttpRequestEventTarget>;
"load": ProgressEvent<XMLHttpRequestEventTarget>;
"loadend": ProgressEvent<XMLHttpRequestEventTarget>;
"loadstart": ProgressEvent<XMLHttpRequestEventTarget>;
"progress": ProgressEvent<XMLHttpRequestEventTarget>;
"timeout": ProgressEvent<XMLHttpRequestEventTarget>;
}
interface XMLHttpRequestEventTarget extends EventTarget {
onabort: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onerror: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onload: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onloadend: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onloadstart: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
onprogress: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
ontimeout: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null;
addEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestEventTarget, ev: XMLHttpRequestEventTargetEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof XMLHttpRequestEventTargetEventMap>(type: K, listener: (this: XMLHttpRequestEventTarget, ev: XMLHttpRequestEventTargetEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
XMLHttpRequest支持DOM 0级事件处理机制和DOM 2级事件处理机制。
DOM 0级事件
- onloadstart
- onload
- onloadend
- onprogress
- onerror
- onabort
- ontimeout
DOM 2级事件
- loadstart: 在接收到响应的第一个字节时触发
- load: 在成功接收完成响应时触发
- loadend: 在通信完成时,且在
error
、abort
或者load
之后触发 - progress: 在接收响应期间反复触发
- error: 在请求出错时触发
- abort: 在调用
abort
方法终止连接时触发 - timeout: 如果设置了
timeout
大于0ms,在响应超时时触发
注意:为保证兼容性,所有事件都应在open
方法执行之前添加好。
跨源资源共享
通过XHR进行Ajax通信的一个主要限制是跨源安全策略。这个安全限制是为了防止某些恶意行为。而跨源资源共享(CORS)定义了浏览器与服务器之间如何实现跨源通信。
跨源资源共享的原理是通过HTTP头部信息,允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败。最基本的头部信息是origin
字段。该字段包含发送请求的页面源(协议、域名和端口),以便服务器确定是否为其提供响应。
为了安全起见,跨域XHR对象施加了一些额外限制:
- 不能使用
setRequestHeader
方法设置自定义头部。 - 不能发送和接收
Cookie
。 getAllResponseHeaders
方法始终返回空字符串。
CORS通过一种叫预检请求的服务器验证机制。以下情况会触发预检机制:
- 自定义头部
- 除GET和POST之外的方法
- 不同请求体内容类型
预检请求使用OPTIONS
方法,并包含以下头部:
- Origin:与简单请求相同。
- Access-Control-Request-Method:请求希望使用的方法。
- Access-Control-Request-Headers:(可选)要使用的自定义头部(逗号分隔的列表)。
这个请求发送后,服务器可以确定是否允许这种类型的请求。在响应中,服务器会返回如下头部信息:
- Access-Control-Allow-Origin:与简单请求相同。
- Access-Control-Allow-Methods: 允许的方法(逗号分隔的列表)。
- Access-Control-Allow-Headers:服务器允许的头部(逗号分隔的列表)。
- Access-Control-Max-Age:缓存预检请求的秒数。
上面说到了CORS请求默认情况下会存在一些限制,可以通过将withCredentials
属性设置为true
来表明请求会发送凭据(Cookie、HTTP认证和客户端SSL证书)。如果服务器允许带凭据的请求,那么可以在响应中包含如下HTTP头部:
- Access-Control-Allow-Credentials: true
如果发送了凭据请求而服务器返回的响应中没有这个头部,则浏览器不会把响应交给JavaScript(responseText为空,status是0,onerror被触发)。
早期的跨域方案
在没有CORS之前,开发者需要利用DOM特性发送跨域请求。
- 图片探测
- JSONP
注意事项
- 对于HTTP请求状态的判断,请使用status而不是statusText,因为后者已经被证明在跨浏览器的情况下不可靠。
- 无论是什么响应内容类型,responseText属性始终都会保存响应体,而responseXML属性不一定。
- response属性会根据responseType来自动序列化数据。目前来看,兼容性不成问题,所以推荐使用。
- 为了保证跨浏览器兼容,
onreadystatechange
事件处理程序应该在open
方法之前赋值。 - 为了保证请求头部被发送,
setRequestHeader
方法必须在open
方法之后、send
方法之前被调用。 - POST请求相比GET请求要占用更多的资源。从性能方面说,发送相同数量的数据,GET请求比POST请求要快两倍。
Content-Type
如果是application/x-www-formurlencoded
,使用URLSearchParams
来创建数据;如果是multipart/form-data
,使用FormData
来创建数据。
参考
- 【MDN】XMLHttpRequest
- JavaScript高级程序设计