JS 三座大山之 Ajax

195 阅读2分钟

ajax 单独用英语念就是阿贾克斯, 而阿贾克斯是一个足球俱乐部, 叫起来也郎朗上口, 所以中国的程序员都称 ajax 为阿贾克斯, 它是一种前后端交互的异步优化技术, 能达到不刷新网页就可以往后端传数据实现交互, 大大提高了用户体验, 这种技术的概念是由一位美国人 杰西·詹姆士·贾瑞特 提出来的, 他原来是一名设计师, 然后为了提高用户体验而想出来的 ajax 构想, 详见 维基百科

XMLHttpRequest

这是 js 内置的 ajax 对象, 他构造出来的对象就可以实现一个 ajax

const request = new XMLHttpRequest();
request.open('GET', '/a/b/c?name=ff', true);
request.onreadystatechange = function() {
  if (request.readyState === 4 && request.status === 200) {
    console.log(request.responseText);
  }
};
request.send();

resquest 是 XMLHttpRequest 的实例, 他有很多种方法

  • open: 初始化一个请求, 参数包括 请求方法, 请求路径, 是否异步, 用于认证的用户, 用于认证的密码
  • onreadystatechange:当状态发生变化时, 会触发这个事件, 主要用来判断请求是否成功
  • readyState: 代表请求的状态码, 一共有 5 种
    • 0: 代理被创建, 但是未被调用 open 方法
    • 1: open 方法被调用
    • 2: send 方法已经被调用, 响应头和响应状态已经获得
    • 3: 下载中响应体中
    • 4: 下载操作完成, 表示整个请求已完成
  • status: 响应中的数字状态码, 也就是我们常说的 HTTP 状态码
  • send: 发送请求

这样一个简单的 ajax 请求就已完成

进阶 Ajax

上一节实现的是 GET 请求, 那么众所周知请求除了 GET 还分很多种, POST, PUT, DELETE, OPTIONS, 等等, 还有其他请求有的会带上请求体, 那么上面的方法已经不能满足所需, 所以我们实现一个支持多种方法的 ajax

function ajax(options) {
  let method = options.method || 'GET';
  let params = options.params;
  let data = options.data;
  let url = options.url + (params ? `?${Object.keys(params).map((key) => key + '=' + params[key]) .join('&')}` : '')
  let isAsync = options.async === false ? false : true;
  let success = options.success;
  let fail = options.fail
  let headers = options.headers;
  let xhr;
  if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
  } else {
    xhr = new ActiveXObject('Microsoft.XMLHTTP');
  }
  xhr.onreadystatechange = () => {
    if (xhr.readyState === 4 && xhr.status === 200) {
      success && success(xhr.responseText);
    }
  };
  xhr.onerror = (err) => {
    fail && fail(err)
  };
  xhr.open(method, url, isAsync);
  if (headers) {
    Object.keys(headers).forEach((key) =>
      xhr.setRequestHeader(key, headers[key])
    );
  }
  method === 'GET' ? xhr.send() : xhr.send(data);
}

我们实现一个封装方法, 方法接收一个对象, 然后把根据对象里的数据来判断何种请求以及所带的数据

options 里的属性:

  • method: 请求方法
  • params: 请求参数
  • url: 请求路径
  • async: 是否异步
  • success: 成功回调
  • fail: 失败回调
  • headers: 设置请求头
  • data: 请求体

然后根据这些属性判断是否存在做进一步的操作, 我在代码里做了兼容 IE 的操作, ActiveXObject 是 IE 里的 ajax 构造函数, 这也是面试经常问的一个考点

里面有一些 api 我没有介绍, 这时候需要你去 mdn 上看文档, 学会自己看文档是一个很重要的能力

等你把这个封装函数消化透你就会发现这其实就是简单封装了一下 XMLHttpRequest 生成对象, 然后做的一系列操作, 没有什么难点

就像现在前端经常用的 ajax 库 axios 一样, 它的核心原理也就是这样, 不同的是它做了很多边缘化的处理和很好的错误处理, 而且还加上了 Promise, 使得异步调用很方便, 而且代码可读性很好, 接下来我们就让这个函数返回一个 Promise, 我们也可以实现链式调用了

function ajax(options) {
  return new Promise((resolve, reject) => {
    let method = options.method || 'GET';
    let params = options.params;
    let data = options.data;
    let url =
      options.url +
      (params
        ? `?${Object.keys(params)
            .map((key) => key + '=' + params[key])
            .join('&')}`
        : '');
    let isAsync = options.async === false ? false : true;
    let success = options.success;
    let fail = options.fail;
    let headers = options.headers;
    let xhr;
    if (window.XMLHttpRequest) {
      xhr = new XMLHttpRequest();
    } else {
      xhr = new ActiveXObject('Microsoft.XMLHTTP');
    }
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4 && xhr.status === 200) {
        success && success(xhr.responseText);
        resolve(xhr.responseText)
      }
    };
    xhr.onerror = (err) => {
      fail && fail(err);
      reject(err);
    };
    xhr.open(method, url, isAsync);
    if (headers) {
      Object.keys(headers).forEach((key) =>
        xhr.setRequestHeader(key, headers[key])
      );
    }
    method === 'GET' ? xhr.send() : xhr.send(data);
  });
}

在整个函数外面套一个 Promise, 然后成功时把响应数据传进 resolve, 失败时传进 reject 里

这样就实现了一个简易的 axios, 不足的是错误处理, 返回数据美化等功能没有细致化实现, 如果你感兴趣的话, 可以去看看 axios 的源码, 然后自己动手实现一下