谈谈原生 Ajax

前言

之前写 Ajax 交互,都是使用已经封装好的库,今天就来聊聊原生的 Ajax。 Ajax 就是 Asynchronous JavaScript + XML 的简写,这一技术能向服务器请求额外的数据而无需重载页面。也就是用户无需刷新页面,便能从服务器获取数据的更新,这样能使用户得到更好的体验。比如有时候,会有这样的一个需求:只想要改变页面的某个区域。而以前的技术只能通过刷新页面来实现,向服务器重新请求页面,这样会增加多余的一些请求,从而影响性能。而 Ajax 则是为了解决这一问题诞生的,是网页无需刷新页面,使用 JavaScript 与服务器进行交互的一种技术。


XMLHttpRequest 对象

Ajax 技术的核心是 XMLHttpRequest 对象(简称 XHR),XHR 为向服务器发送请求和解析服务器的响应提供了相当流畅的接口。
IE7+、 Firefox、 Opera、 Chrome 和 Safari 都支持原生的 XHR 对象,在这些浏览器中创建 XHR 对象只需 new 一下即可。

1
let xhr = new XMLHttpResquest();

当然,如果在更早的 IE 版本(可恶)中使用 XHR,就需要自定义一个创建 XHR 对象的方法了,同时在 IE11 之前也不能使用 ES6 的语法,所以就如同下面这般,先判断 XHR 是否存在:

1
2
3
4
5
6
7
8
9
10
function createXHR() {
// IE7+、Firefox、 Opera、 Chrome 和 Safari···
if (window.XMLHttpRequset) {
return new XMLHttpRequset;
} else {
// 兼容IE5、IE6
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
var XHR = createXHR();


XHR 的用法

open() 方法:

在使用 XHR 时,第一个调用的方法就是 open(method, url, async),该方法接受三个参数:第一个就是发送的请求的类型如 GET 、POST 等,第二个则是提交的 url,第三个为是否异步发送的布尔值,默认为 true。

1
xhr.open("GET", "/api/login", false);

这行代码会启动一个针对 /api/login 的 GET 的请求,以同步的方式执行,不过调用 open() 并不会真正的去发送一个请求,而是启动一个请求准备发送。

send() 方法

要想真正的发送一个请求就得调用 send(String) 方法,该方法接受一个参数,即请求主题发送的数据。
在红宝石一书中提到过:如果不需要通过请求主体发送数据,则必须传入 null ,因为这个参数对有些浏览器来说是必需的

1
2
xhr.open("GET", "test.txt", false);
xhr.send(null);

响应数据

收到服务器响应后,响应会自动填充为 XHR 对象的属性,相关属性为下表:

属性名 说明
responseText 作为响应主体被返回的文本
responseXML 如果响应的内容类型是XML,
这个属性中将保存包含着响应数据的 XML DOM 文档。
对于非 XML 数据而言, responseXML 属性的值将为 null
status 响应的 HTTP 状态
statusText HTTP 状态的说明

在接受响应后,首先要检查的是 status 属性,以确保相应已经成功返回。一般是将 HTTP 状态码 200 作为请求成功的标志,不过状态码为 304 意味着请求的资源没有被修改,可以直接使用浏览器缓存的版本,所以 304 也表示请求成功。请求成功以后 responseText 属性的内容就已经准备就绪了。

1
2
3
4
5
6
7
8
xhr.open("GET", "test.txt", false);
xhr.send(null);

if (xhr.status === 200 || xhr.status === 304) {
console.log(xhr.responseText);
} else {
console.log("请求出错,状态码为:" + xhr.status);
}

onreadystatechange 事件与 readyState 属性

XHR 对象的 readyState 属性是表示当前 请求/响应 的状态,该属性的值及说明为下表:

描述
0 未初始化,尚未调用 open() 方法
1 启动,已经调用 open()方法,但尚未调用 send()方法
2 发送,已经调用 send()方法,但尚未接收到响应
3 接收,已经接收到部分响应数据
4 完成,已经接收到全部响应数据,而且已经可以在客户端使用了

每当 readyState 属性的值发生改变的时候,会触发一次 readystatechange 事件。也就是说,我们可以通过 readystatechange 事件来监听 readyState 属性的值。当 readyState 的值为 4 的时候,说明整个过程已经完成,就可以继续进行后续的数据处理。

1
2
3
4
5
6
7
8
9
10
11
12
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
console.log(xhr.responseText);
} else {
console.log("请求出错,状态码为:" + xhr.status);
}
}
}
xhr.open("GET", "/api/xxx");
xhr.send(null);

在调用 open() 之前指定 onreadystatechange 事件处理程序和使用 xhr 而不是 this 是为了保存浏览器的兼容性。

setRequestHeader() 、getResponseHeader() 与 getAllResponseHeaders() 方法

setRequestHeader() 方法可以设置自定义的 HTTP 请求头部信息,该方法接受两个参数:第一个为头部字段的名称,第二个为头部字段的值。必须在 open() 和 send() 之间调用才能成功发送头部信息

1
2
3
xhr.open("GET", "/api/xxx");
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);

getResponseHeader(“String”) 方法接受一个参数,传入头部字段名称,可以取得相应的响应头部信
息。
getAllResponseHeaders() 方法则可以取得一个包含所有头部信息的长字符串。

1
2
let myHeader = xhr.getResponseHeader("MyHeader");
let allHeaders = xhr.getAllResponseHeaders();


GET 请求

GET 是最常见的请求类型,一般用于查询某些信息。
原生 Ajax 处理如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1.创建 XHR 对象
let xhr = new XMLHttpRequest();

// 4.注册事件 onreadystatechange ,readyState 属性状态改变就会调用
xhr.onreadystatechange = () => {
// 监听 readyState 的值
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
// 5.如果进到这里,说明请求与响应完成,可以继续进行其他处理
console.log(xhr.responseText);
} else {
// 其他问题
console.log("请求出错,状态码为:" + xhr.status);
}
}
}

// 2.启动一个请求,url最好使用 encodeURI 处理一下,以防出现乱码
xhr.open("GET", url);

// 3.发送请求
xhr.send(null);


POST 请求

POST 请求使用频率仅此于 GET,一般用于向服务器发送需要保存的数据。POST 请求是将数据作为请求主体来提交,这点不同于 GET 。POST 请求的主体可以包含很多的数据,并且格式不限。

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
// 1.创建 XHR 对象
let xhr = new XMLHttpRequest();

// 4.注册事件 onreadystatechange ,readyState 属性状态改变就会调用
xhr.onreadystatechange = () => {
// 监听 readyState 的值
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
// 3.如果进到这里,说明请求与响应完成,可以继续进行其他处理
console.log(xhr.responseText);
} else {
// 其他问题
console.log("请求出错,状态码为:" + xhr.status);
}
}
}

// 2.启动一个请求,并设置请求地址
xhr.open("POST", "/api/xxx");

// post方式传递数据是模仿form表单传递给服务器的,要设置header头协议
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");

// 3.发送请求
let info = 'username=' + 'xxx' + '&email=' + 'xxx';
xhr.send(info);


封装 Ajax

不过不能每次使用都要这样啰嗦一堆,所以我在这里将 Ajax 进行封装。既然进行了封装,就说明以后可能会用得到,所以就要保证浏览器兼容性,同时采用 ES5 的语法。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
* 封装原生 Ajax
*
* 参数说明:
* options = {
* url 提交请求的url
* type 请求的类型,默认为GET
* data 请求体的数据
* async 是否以异步方式进行,默认为true
* success 成功后的处理方法
* error 失败后的处理方法
* }
*
*/


function ajax(options) {
// 传入默认为对象
var options = options || {};
// 请求类型默认为 GET
options.type = options.type.toUpperCase() || "GET";
// 默认为异步请求
options.async = options.async || true;

// 格式化参数的函数
var getParams = function(data) {
var arr = [];
for (param in data) {
//查询字符串中每个参数的名称和值都必须使用 encodeURIComponent()进行编码
arr.push(encodeURIComponent(param) + "=" + encodeURIComponent(data[param]));
}
// 在 url 末尾加上一个随机数,避免相同值使用 IE 缓存
arr.push(("randomNum=" + Math.random()).replace(".", ""));
// 返回带有 & 的查询字符串
return arr.join("&");
}

// 格式化数据
var params = getParams(options.data);

/*
* 创建 XHR 对象
* 兼容 IE5、IE6
*/
var xhr = window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject("Microsoft.XMLHTTP");


// 为 xhr 注册事件 onreadystatechange ,当 readyState 属性状态改变就会触发该事件
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
// 成功
options.success && options.success(xhr.responseText);
} else {
// 失败
options.error && options.fail(status);
}
}
}

// GET 请求
if (options.type === "GET") {
// 启动 GET 一个请求,并设置请求地址
xhr.open("GET", options.url + "?" + params, options.async);
// 发送请求
xhr.send(null);
} else if (options.type === "POST") {
// POST 请求
xhr.open("POST", options.url, options.async);
// 设置请求头,模拟 POST 表单提交
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// 发送请求
xhr.send(param);
}
}

测试:使用 Node.js 创建服务器,对上面的代码进行简单的测试
服务器端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const http = require("http");
const url = require("url");

const server = http.createServer((req, res) => {
// GET 处理
let data = url.parse(req.url, true).query;
res.writeHead(200, {
'Content-Type': 'text/plain',
'Access-Control-Allow-Origin': '*'
});
res.write("姓名:" + data.name + "===" + "年龄:" + data.age);
res.end();
});

server.listen(8080);

客户端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 测试
ajax({
url: "http://localhost:8080/",
type: "GET",
data: {
name: "tflin",
age: "21"
},
async: true,
success: (result) => {
console.log("发送成功!");
console.log(result);
},
error: (result) => {
console.log("发送失败!");
}
});

启动服务器后,刷新客户端
ajax封装测试


总结

  1. Ajax 是无需等待页面就能从服务器获取数据的一种方法。
  2. Ajax 的核心对象是 XMLHttpRequest 对象,简称 XHR 对象
  3. POST 请求需要设置请求头,模拟表单提交
  4. 在请求体数据后面加上时间戳或随机数,是为了避免发送相同的数据时 IE 使用缓存
  5. 使用 Ajax 的的步骤为下:
    1
    2
    3
    4
    5
    1. 创建 XHR 对象
    2. 调用 open() 启动一个请求
    3. 调用 send() 发送请求
    4. 为 xhr 注册 onreadystatechange 时间,监听 readyState 属性的状态(这一步写在 open() 之前,保证浏览器兼容性)
    5. readyState 为 4,status 没什么问题时,说明整个 请求/响应 的过程已经成功
------ 本文结束 ------

版权声明

Tflin's Blog by Tan Feng Lin is licensed under a Creative Commons BY-NC-ND 4.0 International License.
谭丰林创作并维护的Tflin's Blog博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Tflin's Blog 博客( http://tflin.com ),版权所有,侵权必究。