前言

web2.0出现很多年了,XMLHttpRequest也由此诞生,一代的XMLHttpRequest使用至今出现了诸多不足。作为其升级版的二代 XMLHttpRequest 引入了大量的新功能(例如跨源请求、上传进度事件以及对上传/下载二进制数据的支持等),而且 AJAX 可以与很多尖端的 HTML5 API 一起使用。

传统使用

请求一段文本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var xhr;
var url = 'http://localhost/test.txt';
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xhr = new XMLHttpRequest();
} else {
// IE6, IE5 浏览器执行代码
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open('GET', url, true);
xhr.responseType = 'text';
xhr.send();
xhr.onreadystatechange = function () {
console.log(xhr)
if (xhr.readyState === 4) {
if (xhr.status === 200) {
//处理数据
console.log(xhr.responseText)
} else {
//其它操作
console.log('error')
}
}
}

正常的请求流程为:

  1. 判断浏览器是否包含XMLHttpRequest对象,再创建一个浏览器对应的XMLHttpRequest(IE为ActiveXObject)对象
  2. 使用open方法打开一个链接,指定请求方式和是否异步请求
  3. 指定responseType返回数据类型为text,不指定则默认为empty string
  4. 使用send发送请求
  5. 监听onreadystatechange事件,判断其readyState是否为4且status是否为200,如果是则代表请求成功
  6. 使用responseText返回请求的数据

请求JSON格式的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
var url = 'http://localhost/test.json';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.send();
xhr.onreadystatechange = function () {
console.log(xhr)
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.response)
}
}
}

请求JSON数据,返回的response就为此JSON的内容。

针对数据乱码的处理

在请求头中添加设置编码的方法,如

1
xhr.setRequestHeader("Content-Type","charset=gb2312")

请求 - send

使用XMLHttpRequest发送数据,需要使用POST的请求方式。

DOMString

发送一段普通的字符串。

1
2
3
4
5
6
7
8
9
var url = 'http://localhost/getInfo.php';
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'json';
xhr.send('Hello Ajax');
xhr.onload = function(e) {
console.log(e)
}

JSON

发送一段json数据,注意发送之前需要使用JSON.stringify()json字符串化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var url = 'http://localhost/getInfo.php';
var json = {
name: 'xiaoyu',
age: 18
}
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'json';
xhr.send(JSON.stringify(json));
xhr.onload = function(e) {
console.log(e)
}

FormData

XMLHttpRequest2添加了一个新的接口FormData。利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步的提交这个”表单”。比起普通的ajax, 使用FormData的最大优点就是我们可以异步上传一个二进制文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
var url = 'http://localhost/getInfo.php';
var formData = new FormData();
formData.append("name", 'xiaoyu');
formData.append("age", 22); // 注意,传递的时候会作为字符串处理
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'json';
xhr.send(formData);
xhr.onload = function(e) {
console.log(e)
}

Blob和ArrayBuffer

暂未研究。

响应 - 事件

处理方式

通过onreadystatechange事件处理

1
2
3
4
5
6
7
8
9
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
//处理数据
} else {
//其它操作
}
}
}

通过onload 事件处理

1
2
3
4
5
6
7
xhr.onload = function() {
if (xhr.status === 200) {
//处理数据
} else {
//其它操作
}
}

event对象

同普通的交互事件一样,xhr的事件中也包含一个event对象:

1
2
3
xhr.onload = function(e) {
console.log(e)
}

可以看到此对象中也包含响应的数据,不过一般都不会使用event去获取数据。

readyState、status和statusText

readyState

当一个XMLHttpRequest初次创建时,这个属性的值是从0开始,知道接收完整的HTTP响应,这个值增加到4。有五种状态:

  • 状态0 (未初始化): (XMLHttpRequest)对象已经创建或已被abort()方法重置,但还没有调用open()方法;
  • 状态1 (载入):已经调用open() 方法,但是send()方法未调用,尚未发送请求;
  • 状态2 (载入完成): send()方法已调用,HTTP请求已发送到web服务器,请求已经发送完成,未接收到响应;
  • 状态3 (交互):所有响应头部都已经接收到。响应体开始接收但未完成,即可以接收到部分响应数据;
  • 状态4 (完成):已经接收到了全部数据,并且连接已经关闭。

onreadystatechange事件可以监测到readyState的状态变化。

status

这是HTTP的状态码,200为成功,404为找不到页面,500为服务器错误。

statusText

statusText表示HTTP响应状态的描述文本,即OK、Not Found等

getAllResponseHeaders和getResponseHeader

xhr.getAllResponseHeaders()可以获取响应头的所有内容

xhr.getResponseHeader(name)可以获取指定的响应头内容

如:

1
2
3
4
5
6
7
8
9
10
11
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.getAllResponseHeaders())
console.log(xhr.getResponseHeader('date'))
console.log(xhr.response)
} else {
console.log('error')
}
}
}

响应 - responseType

类型 描述
empty string 空字符串,这是默认值
arraybuffer 二进制缓冲数组
blob 二进制大对象
document 文档类型
json JSON类型
text 文本类型

注意:

responseTypetext或者empty string类型时可以使用responseText属性,为其它类型时调用responseText会发生异常;

responseTypedocument或者empty string类型时可以使用responseXML属性,为其它类型时调用responseXML会发生异常;

responseType不是empty stringtextdocument类型时,需要转换成具体的类型进行解析。

responseType新类型 - Blob

前段时间做项目,需要请求二进制数据,在服务器中存储的是多媒体文件形式。此时需要将responseType指定为blob,才能正确接收。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
var url = 'http://localhost/test.amr';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.send();
xhr.onreadystatechange = function () {
console.log(xhr)
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.response)
}
}
}

此时可以看到控制台打印出:

注意到此时xhr没有了responseText属性,而是直接使用response接收,返回的是一个Blob二进制数据。

处理图像类型文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var url = 'http://localhost/test.png';
window.URL = window.URL || window.webkitURL;
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function() {
var blob = xhr.response;
var img = document.createElement('img');
img.onload = function(e) {
window.URL.revokeObjectURL(img.src);
}
img.src = window.URL.createObjectURL(blob);
document.body.appendChild(img);
}

URL.createObjectURL() 会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。

URL.revokeObjectURL() 静态方法用来释放一个之前通过调用 URL.createObjectURL() 创建的已经存在的 URL 对象。当你结束使用某个 URL 对象时,应该通过调用这个方法来让浏览器知道不再需要保持这个文件的引用了。

你可以在sourceopen被处理之后的任何时候调用revokeObjectURL()。这是因为createObjectURL()仅仅意味着将一个媒体元素的src属性关联到一个 MediaSource 对象。调用revokeObjectURL() 使这个潜在的对象保留在原来的地方,允许平台在合适的时机进行垃圾收集。

处理音频类型文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var url = 'http://localhost/audio.mp3';
window.URL = window.URL || window.webkitURL;
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function() {
var blob = xhr.response;
var audio = document.createElement('audio');
audio.onload = function(e) {
window.URL.revokeObjectURL(audio.src);
}
audio.src = window.URL.createObjectURL(blob);
audio.controls = 'controls';
document.body.appendChild(audio);
}

注意: HTML5直接支持的音频文件类型只有: mp3oggwav

处理视频类型文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var url = 'http://localhost/test.mp4';
window.URL = window.URL || window.webkitURL;
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.send();
xhr.onload = function() {
var blob = xhr.response;
var video = document.createElement('video');
video.onload = function(e) {
window.URL.revokeObjectURL(video.src);
}
video.src = window.URL.createObjectURL(blob);
video.controls = 'controls';
document.body.appendChild(video);
}

注意: HTML5直接支持的视频文件类型只有: MP4WebMOgg

responseType新类型 - ArrayBuffer

ArrayBuffer 是二进制数据通用的固定长度容器。如果您需要原始数据的通用缓冲区,ArrayBuffer 就非常好用,但是它真正强大的功能是让您使用 JavaScript 类型数组创建底层数据的“视图”。实际上,可以通过单个ArrayBuffer 来源创建多个视图。

1
2
3
4
5
6
7
8
9
10
var url = 'http://localhost/test.png';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.send();
xhr.onload = function() {
console.log(xhr.response);
var blob = new Blob([xhr.response]);
console.log(blob)
}

responseType设置为arraybuffer即可返回arraybuffer类型的数据。

可以使用Blob构造函数将arraybuffer的数据转化为Blob,注意得加上中括号[]

注意:

以前有一个BlobBuilder对象,用于处理将ArrayBuffer转化为Blob格式,不过我测试的时候所有浏览器都报错,查了下MDN发现此对象已经废弃。所以开发的时候最好还是看W3C文档或者MDN,有助于了解API的实时更新。

类型化数组

类型化数组(Typed Arrays)是JavaScript中新出现的一个概念,专为访问原始的二进制数据而生。

类型数组的类型有:

名称 大小 (以字节为单位) 说明
Int8Array 1 8位有符号整数
Uint8Array 1 8位无符号整数
Int16Array 2 16位有符号整数
Uint16Array 2 16位无符号整数
Int32Array 4 32位有符号整数
Uint32Array 4 32位无符号整数
Float32Array 4 32位浮点数
Float64Array 8 64位浮点数

本质上,类型化数组和ArrayBuffer是一样的。不过一个可读写(脱掉buffer限制),一个当数据源的命。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var url = 'http://localhost/test.png';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.send();
xhr.onload = function() {
console.log(this.response)
var uInt8Array = new Uint8Array(this.response)
console.log(uInt8Array)
var int8Array = new Int8Array(this.response)
console.log(int8Array)
var uInt16Array = new Uint16Array(this.response)
console.log(uInt16Array)
var int16Array = new Int16Array(this.response)
console.log(int16Array)
var int32Array = new Int32Array(this.response)
console.log(int32Array)
var float32Array = new Float32Array(this.response)
console.log(float32Array)
var float64Array = new Float64Array(this.response)
console.log(float64Array)
}

responseType新类型 - Document

当请求一段具有格式的htmlxml格式的数据时,可以指定responseTypeDocument,解析为一段文档。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
var url = 'http://localhost/test.html';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'document';
xhr.send();
xhr.onreadystatechange = function () {
console.log(xhr)
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.response)
}
}
}

可以看到控制台打印:

注意到此时xhr多了一个responseXML属性:

其中比较有用的属性有:

responseXML.all

responseXML.doctype

responseXML.documentElement

其他可能有用的属性还包括:

返回HTMLCollection类型的属性:

1
childNodes、children、images、forms、links、plugins、scripts、embeds

返回StyleSheetList类型的属性:

1
styleSheets

返回NodeList类型的属性:

1
childNodes

其他属性慢慢探索。

CORS跨域请求

以前都是使用jsonp动态生成script标签进行跨域资源获取,而XMLHttpRequest2可以使用CORS(跨域资源共享,Cross-Origin Resource Sharing)实现跨域请求。

通常在后端语言(如: PHP)或者是服务器(如: Apache)配置文件中都可以设置CORS

1
2
3
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type

在PHP中设置

1
header("Access-Control-Allow-Origin:*");

在Apache中设置

1
2
3
4
5
<Directory />
Options FollowSymLinks
AllowOverride None
Header set Access-Control-Allow-Origin *
</Directory>

service httpd restart重启服务

在Nginx中设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#
# Wide-open CORS config for nginx
#
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}

service nginx restart重启服务

参考资料

XMLHttpRequest2 新技巧

XML DOM - XMLHttpRequest 对象

史上最全的AJAX之XMLHttpRequest方法和属性详解

理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型

Ajax之不可或缺的setRequestHeader()

XMLHTTP中setRequestHeader参数问题

readyState与status

FormData 对象的使用

Window.URL

HTTP访问控制(CORS)

跨域资源共享 CORS 详解

CORS 跨域 实现思路及相关解决方案

Apache2 同源策略解决方案 - 配置 CORS

HTML5 Blob与ArrayBuffer、TypeArray和字符串String之间转换