模仿 JQuery 封装的一个简单的 Ajax 库,进行实战练习,深入理解 Ajax,复习库和插件的封装。

参数设置

options: [Object]

  • type: [String] 请求方式,默认 get

  • url: [String] 请求地址

  • data: [Object] 发送的数据

  • dataType: [String] 数据类型,json/xml/text,默认 json

  • cache: [Boolean] 是否缓存,默认不缓存

  • async: [Boolean] 同步或异步,默认异步

  • success: [Function] 请求成功的回调,传入获取的数据和 XHR 对象

  • error: [Function] 请求失败的回调,传入错误信息和 XHR 对象

整体结构

使用一个闭包封装,然后通过挂载到 window 来使用,借用了 JQuery 的思想

  1. ;(function(window) {
  2. function ajax(options) {
  3. //...
  4. }
  5. window.ajax = ajax;
  6. })(window)

同样借助 JQuery 的思想,实现 ajax 既可以用作普通函数,又可以用作构造函数,使用都会返回一个 init 实例(也就是 ajax 实例)

  1. function ajax(options) {
  2. return new init(options); //=> 返回
  3. }
  4. let init = function init(options) {
  5. //...
  6. };
  7. ajax.prototype = {
  8. constructor: ajax,
  9. init,
  10. ...
  11. };
  12. init.prototype = ajax.prototype;

构造函数

首先解析出配置对象,并且赋予默认值,然后挂载到实例上

  1. let init = function init(options = {}) {
  2. let {
  3. url,
  4. type = 'get',
  5. data = null,
  6. dataType = 'json',
  7. async = true,
  8. cache = false,
  9. success,
  10. error
  11. } = options;
  12. //=> 把配置项挂载到实例上
  13. ['url', 'type', 'data', 'dataType', 'async', 'cache', 'success', 'error'].forEach(item => {
  14. this[item] = eval(item);
  15. });
  16. }

发送 Ajax 请求

分为传统的四步,中间增加了一些过程,这是一个主要的方法,必须执行这个方法才能

  1. sendAjax() {
  2. this.handleData(); //=> 处理发送的数据
  3. this.handleCache(); //=> 处理缓存问题
  4. let {
  5. type,
  6. url,
  7. data,
  8. async,
  9. error,
  10. success
  11. } = this;
  12. //=> 第一步
  13. xhr = new XMLHttpRequest();
  14. //=> 第二步
  15. xhr.open(type, url, async);
  16. //=> 第三步
  17. xhr.onreadystatechange = () => {
  18. if (xhr.readyState === 4) {
  19. //=> error
  20. if (!/^(2|3)\d{2}$/.test(xhr.status)) {
  21. error && error(xhr.statusText, xhr);
  22. return;
  23. }
  24. //=> success
  25. let result = this.handleDataType(xhr); //=> 根据数据类型,处理响应数据
  26. success && success(result, xhr);
  27. }
  28. };
  29. //=> 设置请求头,使得后台接受的参数是 form data,而不是字符串
  30. xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=utf-8');
  31. //=> 第四步
  32. xhr.send(data);
  33. }

处理相关参数

处理 dataType

只处理了 text、json、xml 格式的数据

  1. handleDataType(xhr) {
  2. let dataType = this.dataType.toUpperCase(),
  3. result = xhr.responseText;
  4. //=> 这里只处理了 三种 格式
  5. switch (dataType) {
  6. case 'TEXT':
  7. break;
  8. case 'JSON':
  9. result = JSON.parse(result);
  10. break;
  11. case 'XML':
  12. result = xhr.responseXML;
  13. break;
  14. }
  15. return result;
  16. }

处理 data

首先,传递的数据如果是对象,需要转换为字符串

然后,根据请求方式的不同,传递的方式不同

  • get 类:拼接到 URL 上,发送的数据为 null

  • post 类:直接将字符串的数据发送出去

handleData() {
  let {
    data,
    type
  } = this;
  if (!data) return;
  // 如果是对象,转换为 x-www-form-urlencoded 模式,方便后期传递给服务器
  if (typeof data === 'object') {
    let str = ``;
    for (let key in data) {
      if (data.hasOwnProperty(key)) {
        str += `${key}=${data[key]}&`
      }
    }
    data = str.slice(0, str.length - 1);
  }
  // 根据请求方式的不同,传递数据的方式不同
  if (/^(GET|DELETE|HEAD|TRACE|OPTIONS)$/i.test(type)) {
    this.url += `${this.checkURL()}${data}`;
    this.data = null;
    return;
  }
  this.data = data;
}

处理 cache

只有 get 请求需要进行处理,如果不需要缓存,那么需要在 URL 后面的问号传参追加一个随机参数(这里是时间戳)

handleCache() {
  let {
    type,
    url,
    cache
  } = this;
  if (/^GET$/i.test(type) && cache === false) {
    //=> url 末尾追加时间戳
    url += `${this.checkURL()}_=${+(new Date())}`;
    this.url = url;
  }
}

其他辅助方法

检测 URL中是否存在问号

checkURL() { 
  return this.url.indexOf('?') > -1 ? '&' : '?';
}

代码

;(function (window) {
  function ajax(options) {
    return new init(options);
  }

  let init = function init(options = {}) {
    let {
      url,
      type = 'get',
      data = null,
      dataType = 'json',
      async = true,
      cache = false,
      success,
      error
    } = options;

    //=> 把配置项挂载到实例上
    ['url', 'type', 'data', 'dataType', 'async', 'cache', 'success', 'error'].forEach(item => {
      this[item] = eval(item);
    });
  }

  ajax.prototype = {
    constructor: ajax,
    //=> 初始化以及作为构造函数
    init,
    //=> 发送 Ajax 请求
    sendAjax() {
      this.handleData();
      this.handleCache();
      let {
        type,
        url,
        data,
        async,
        error,
        success
      } = this,
      xhr = new XMLHttpRequest();
      xhr.open(type, url, async);
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4) {
          //=> error
          if (!/^(2|3)\d{2}$/.test(xhr.status)) {
            error && error(xhr.statusText, xhr);
            return;
          }
          //=> success
          let result = this.handleDataType(xhr);
          success && success(result, xhr);
        }
      };
      xhr.send(data);
    },
    //=> 处理 dataType
    handleDataType(xhr) {
      let dataType = this.dataType.toUpperCase(),
        result = xhr.responseText;

      //=> 这里只处理了 三种 格式
      switch (dataType) {
        case 'TEXT':
          break;
        case 'JSON':
          result = JSON.parse(result);
          break;
        case 'XML':
          result = xhr.responseXML;
          break;
      }
      return result;
    },
    //=> 处理 cache
    handleCache() {
      let {
        type,
        url,
        cache
      } = this;
      if (/^GET$/i.test(type) && cache === false) {
        //=> url 末尾追加时间戳
        url += `${this.checkURL()}_=${+(new Date())}`;
        this.url = url;
      }
    },
    //=> 处理 data
    handleData() {
      let {
        data,
        type
      } = this;
      if (!data) return;
      // 如果是对象,转换为 x-www-form-urlencoded 模式,方便后期传递给服务器
      if (typeof data === 'object') {
        let str = ``;
        for (let key in data) {
          if (data.hasOwnProperty(key)) {
            str += `${key}=${data[key]}&`
          }
        }
        data = str.slice(0, str.length - 1);
      }
      // 根据请求方式的不同,传递数据的方式不同
      if (/^(GET|DELETE|HEAD|TRACE|OPTIONS)$/i.test(type)) {
        this.url += `${this.check()}${data}`;
        this.data = null;
        return;
      }
      this.data = data;
    },
    //=> 检测 URL中是否存在问号
    checkURL() {
      return this.url.indexOf('?') > -1 ? '&' : '?';
    }
  }
  //=> 借鉴 JQuery,使得 new ajax 或者直接使用 ajax 都会返回 ajax 的实例
  init.prototype = ajax.prototype;
  window.ajax = ajax;
})(window)