Ajax 不是 JavaScript 的规范,它是 Jesse James Garrett 提出的新术语:Asynchronous JavaScript and XML
,意思是用 JavaScript 执行异步网络请求。
# 网络请求的发展
网络请求,是用来从服务端获取需要的信息,然后解析协议和内容,来进行页面渲染或者是信息获取的过程。前面已经大致说过关于浏览器渲染,以及完整的 HTTP 请求流程。
在很久以前,我们的网络请求除了静态资源(HTML/CSS/Javascript 等)文件的获取,主要用于表单的提交。我们在完成表单内容的填写之后,点击提交按钮,表单开始提交,浏览器就会刷新页面,然后在新页面里告诉你操作是成功了还是失败了。
除此之外,同步请求会阻塞进程,导致页面呈现假死状态,使得用户体验变得糟糕。为了避免这种情况,我们开始使用异步请求 + 回调
的方式,来进行请求处理。
随着时间发展,Ajax 的应用越来越广,如今使用 Ajax 已经是前端开发的基本操作。但 Ajax 是一种解决方案,在前端中的具体实现依赖使用XMLHttpRequest
相关 API。页面开始支持局部更新、动态加载,甚至还有懒加载、首屏加载等等,都是以XMLHttpRequest
为前提。
# XMLHttpRequest
XMLHttpRequest
让发送一个 HTTP 请求变得非常容易。你只需要简单的创建一个请求对象实例,打开一个 URL,然后发送这个请求。当传输完毕后,结果的 HTTP 状态以及返回的响应内容也可以从请求对象中获取。
来看个简单的例子:
var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象
request.onreadystatechange = function () {
// 状态发生变化时,函数被回调
if (request.readyState == 4) {
// 成功完成
// 判断响应结果:
if (request.status == 200) {
// 成功,通过responseText拿到响应的文本
console.log(request.responseText);
} else {
// 失败,根据响应码判断失败原因:
console.log(request.status);
}
}
};
// 发送请求
// open的参数:
// 一:请求方法,包括get/post等
// 二:请求地址
// 三:表示是否异步请求,若为false则是同步请求,会阻塞进程
request.open("GET", "/api/categories", true);
request.send();
上面是处理一个 HTTP 请求的方法。我们通常会将它封装成一个通用的方法,方便调用。上面例子中使用200
来判断是否成功,但有些时候200-400
(不包括400
)的范围,都可以算是成功的。
我们将其封装起来,同时使用 ES6 的Promise
的方式来操作的话,会变成这样:
function ajax({ method, url, params, contentType }) {
const xhr = new XMLHttpRequest();
const formData = new FormData();
Object.keys(params).forEach((key) => {
formData.append(key, params[key]);
});
return new Promise((resolve, reject) => {
try {
xhr.open(method, url, false);
xhr.setRequestHeader("Content-Type", contentType);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 400) {
// 这里我们使用200-400来判断
resolve(xhr.responseText);
} else {
// 返回请求信息
reject(xhr);
}
}
};
xhr.send(formData);
} catch (err) {
reject(err);
}
});
}
这里使用了FormData
来处理。通过FormData
对象可以组装一组用XMLHttpRequest
发送请求的键/值对。它可以更灵活方便的发送表单数据,因为可以独立于表单使用。如果你把表单的编码类型设置为multipart/form-data
,则通过FormData
传输的数据格式和表单通过submit()
方法传输的数据格式相同,也支持文件的上传和添加。
上面的代码也只是一个简单的例子,如果要封装成完善的库,我们通常还需要处理一些问题:
- 浏览器兼容性
- babel polyfill 处理 ES6
- Get 方法通过将 params 转换拼接 URL 处理
除了FormData
以外,我们同样可以通过其他方式来提交请求,可能涉及序列化等操作,具体方式会根据每个团队的后台情况不一样而不同。