【axios三部曲】三、极简核心造轮子

axios执行流程

目录结构

image.png

如果要一个极简核心的话,我们只需要 request 请求函数,和一个 XMLHttpRequest 即可,拦截器,适配器 dispatchRquest 都可以不要

Axios构造函数

我们从Axios构造函数开始写
core/Axios.js

import utils from "../utils";
import mergeConfig from "./mergeConfig";
import xhr from "./xhr";

// 核心文件

function Axios(config) {
  this.defaults = config;
}

Axios.prototype.request = function (config) {
  // 处理axios(url[,config])
  if (typeof config === "string") {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  // 合并config
  config = mergeConfig(this.defaults, config);

  console.log(config);

  // 设置默认method
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = "get";
  }

  let promise = Promise.resolve(config);
  promise = promise.then(xhr);

  return promise;
};

utils.forEach(["delete", "get", "head", "options"], function (method) {
  Axios.prototype[method] = function (url, config) {
    return this.request(
      utils.merge(config || {}, {
        method: method,
        url: url,
      })
    );
  };
});

utils.forEach(["post", "put", "patch"], function (method) {
  Axios.prototype[method] = function (url, data, config) {
    return this.request(
      utils.merge(config || {}, {
        method: method,
        url: url,
        data: data,
      })
    );
  };
});

export default Axios;

只有一个最简单request方法

mergeConfig

core/mergeConfig.js
也没有做过多的判断,只是优先把config2的值拷贝到config上,其次再使用config1的值

import utils from "../utils";

// 合并两个配置对象,返回一个新的对象
function mergeConfig(config1, config2) {
  let config = {};

  let axiosKeys = Array.from(
    new Set(Object.keys(config1).concat(Object.keys(config2)))
  );

  // 优先使用config2身上的
  utils.forEach(axiosKeys, function (prop) {
    if (utils.isObject(config2[prop])) {
      config[prop] = utils.merge(config1[prop], config2[prop]);
    } else if (typeof config2[prop] !== "undefined") {
      config[prop] = config2[prop];
    } else if (utils.isObject(config1[prop])) {
      config[prop] = utils.merge(config1[prop]);
    } else if (typeof config1[prop] !== "undefined") {
      config[prop] = config1[prop];
    }
  });

  return config;
}

export default mergeConfig;

xhr

core/xhr.js
一个简单的XMLHttpRequest

// XMLHttpRequest

// 返回一个promise
const xhr = function (config) {
  return new Promise(function (resolve, reject) {
    let xhr = new XMLHttpRequest();

    xhr.open(config.method, config.url);
    xhr.send(config.data);

    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          // 返回response数据,自定义格式
          resolve({
            config: config,
            data: JSON.parse(xhr.responseText),
            request: xhr,
            headers: xhr.getAllResponseHeaders(),
            status: xhr.status,
            statusText: xhr.statusText,
          });
        } else {
          reject("数据请求错误:" + xhr.statusText);
        }
      }
    };
  });
};

export default xhr;

utils

utils.js
一些工具函数

function forEach(obj, fn) {
  if (obj === null || typeof obj === "undefined") {
    return;
  }

  if (typeof obj !== "object") {
    obj = [obj];
  }

  if (Array.isArray(obj)) {
    for (let i = 0; i < obj.length; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    for (let key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

function bind(fn, thisArg) {
  // 这里用fn.apply来实现函数this绑定,注意需要返回的也是一个函数,所以这里包一层
  return function wrap() {
    // 把多个参数,转成数组的形式
    let args = new Array(arguments.length);
    for (let i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }

    // args是一个数组,apply调用方式
    return fn.apply(thisArg, args);
  };
}

// 实现继承,b->a 如果是函数要绑定this指向
function extend(a, b, thisArg) {
  forEach(b, function (val, key) {
    if (thisArg && typeof val === "function") {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

// 合并对象,返回一个新的对象
function merge() {
  let result = {};

  function assignValue(val, key) {
    // result里的是一个对象,并且val也是一个对象,深拷贝
    if (typeof result[key] === "object" && typeof val === "object") {
      result[key] = merge(result[key], val);
    } else if (typeof result[key] === "object") {
      result[key] = merge({}, val);
    } else {
      result[key] = val;
    }
  }

  // 有多个参数要合并,依次循环遍历,合并到result
  for (let i = 0; i < arguments.length; i++) {
    forEach(arguments[i], assignValue);
  }

  return result;
}

function isObject(val) {
  return val !== null && typeof val === "object";
}

export default {
  forEach,
  bind,
  extend,
  merge,
  isObject,
};

defaults

defaults.js
默认的头配置文件

let defaults = {
  method: "get",
};

export default defaults;

axios入口文件

// 入口文件
import Axios from "./core/Axios";
import utils from "./utils";
import mergeConfig from "./core/mergeConfig";
import defaults from "./defaults";

function createInstance(defaultConfig) {
  let context = new Axios(defaultConfig);
  let instance = utils.bind(Axios.prototype.request, context);

  // extend prototype
  utils.extend(instance, Axios.prototype, context);
  // extend Axios属性
  utils.extend(instance, context);

  instance.create = function (newConfig) {
    return createInstance(mergeConfig(defaultConfig, newConfig));
  };

  return instance;
}

let axios = createInstance(defaults);

console.dir(axios);

// console.log(axios);

// axios({
//   url: "http://localhost:3000/posts",
//   method: "get",
// }).then((response) => {
//   console.log(response);
// });

axios
  .get("http://localhost:3000/posts", {
    headers: {
      common: 111,
    },
  })
  .then((response) => {
    console.log(response);
  });

总结

  • promise的使用
  • utils里面的工具函数比较实用,要注意的是forEach,bind,extend,merge这几个函数
  • 构造函数 + prototype的方式来组织代码,现在已经有更好的选择 class
  • axios库的核心就是axios是一个函数(request),它身上也挂有方法(get,post),我们可以学习这种编程方法
  • axios的config参数合并也是重点,有优先级,默认的最低,最后方法传入的最高

源码地址

posted @ 2022-02-26 16:38  Tonysoul  阅读(93)  评论(0编辑  收藏  举报