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以外,我们同样可以通过其他方式来提交请求,可能涉及序列化等操作,具体方式会根据每个团队的后台情况不一样而不同。