写点什么

学习 axios 源码(一)

作者:yuanyxh
  • 2024-09-14
    中国香港
  • 本文字数:3473 字

    阅读完需:约 11 分钟

学习 axios 源码(一)

前言

axios 版本 1.3.4,建议阅读 axios 文档,熟悉相关 Api 后进行学习。

追溯 axios 初始化

我们在项目中使用 axios 时,一般都是安装导入然后对其进行二次封装,这说明它默认导出了一个 axios 对象。打开 axios 源码的 package.json 文件查看 main 字段,可以知道它的入口文件是同路径下的 index.js,里面的内容不多,粗略如下:


// index.jsimport axios from './lib/axios.js';
const { /* omission */ } = axios;
export default { axios as default, /* omission */}
复制代码


它默认导出了一个 axios 对象,这个对象来自 lib/axios.js,里面的代码省略如下:


// lib/axios.jsimport defaults from './defaults/index.js';
function createInstance(defaultConfig) { /* do something */}
const axios = createInstance(defaults);
export default axios;
复制代码


可以看到我们使用的 axios 是由 createInstance 函数返回的,那么它在内部做了什么呢?


// lib/axios.jsimport utils from './utils.js';import bind from './helpers/bind.js';import Axios from './core/Axios.js';import mergeConfig from './core/mergeConfig.js';
function createInstance(defaultConfig) { const context = new Axios(defaultConfig); const instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });
// Copy context to instance utils.extend(instance, context, null, { allOwnKeys: true });
// Factory for creating new instances instance.create = function create(instanceConfig) { return createInstance(mergeConfig(defaultConfig, instanceConfig)); };
return instance;}
复制代码


可以看到在这个函数内部主要做了 5 件事,分别是:


  1. 构造一个 Axios 实例

  2. 调用自封装的 bind 函数,传入 Axios 原型的 request 方法和 Axios 实例,返回一个新的函数

  3. 拷贝 Axios 原型的自有数据至这个新函数,并将其中函数的 this 指向 Axios 实例

  4. 拷贝 Axios 实例的自有数据至这个新函数

  5. 给这个新函数添加一个 create 方法,也就是我们常用的创建 axios 副本方法


最后返回这个函数,也就是我们使用的 axios;它并不是 Axios 实例,但存在 Axios 原型和 Axios 实例的所有数据。

构造 Axios 实例

第一步是构造 Axios 的实例, Axios 定义在 lib/core/Axios.js 中,它的简略代码如下:


// lib/core/Axios.jsclass Axios {  constructor(instanceConfig) {    this.defaults = instanceConfig;    this.interceptors = {      request: new InterceptorManager(),      response: new InterceptorManager()    };  }
request() { /* omission */ }
getUri() { /* omission */ }}
复制代码

配置项

这里我们忽略 Axios 原型的 requestgetUri 方法;可以看到在 constructor 方法内将传入的数据赋值给了 defaults 属性,这里的数据来自 lib/defaults/index.js;其中是默认的一些配置,比如常用的 baseURLtimeout 等;我们可以通过 defaults 属性修改这些配置,比如:


axios.defaults.baseURL = 'https://yuanyxh.com/';
// orconst request = axios.create({ baseURL: 'https://yuanyxh.com/', timeout: 60000});
复制代码

拦截器

添加 defaults 属性的下一行又添加了 interceptors 属性,它被赋值为一个包含 requestresponse 属性的对象,这两个属性的值是 InterceptorManager 的实例,InterceptorManager 定义在 lib/core/InterceptorManager.js,代码粗略如下:


class InterceptorManager {  constructor() {    this.handlers = [];  }
use(fulfilled, rejected, options) { /* omission */ }
eject(id) { /* omission */ }
clear() { /* omission */ }
forEach(fn) { /* omission */ }}
复制代码


有没有觉得很熟悉?这其实就是我们常用的请求、响应拦截器;InterceptorManager 原型方法的作用分别如下:


  • use: 注册一个请求或响应拦截器,将 fulfilledrejected, options 合并为一个对象并添加至 handlers 数组,返回索引。

  • eject: 从 handlers 数组中清除指定索引的请求或响应拦截器

  • clear: 重置 handlers 数组

  • forEach: 遍历数组并将数组项传入 fn 回调中


所以我们才能通过 axios.interceptors.(request | response).use 注册拦截器并处理请求或响应数据。

请求别名

Axios 类外部,还给 Axios 原型添加了我们常用的 getpost 等方法:


// lib/core/Axios.jsutils.forEach(  ['delete', 'get', 'head', 'options'],  function forEachMethodNoData(method) {    Axios.prototype[method] = function (url, config) {      return this.request(        mergeConfig(config || {}, {          method,          url,          data: (config || {}).data        })      );    };  });
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { function generateHTTPMethod(isForm) { return function httpMethod(url, data, config) { return this.request( mergeConfig(config || {}, { method, headers: isForm ? { 'Content-Type': 'multipart/form-data' } : {}, url, data }) ); }; }
Axios.prototype[method] = generateHTTPMethod();
Axios.prototype[method + 'Form'] = generateHTTPMethod(true);});
复制代码


可以看到我们常用的 getpost 等方法最终都是调用了 request 方法,它们只是一个过渡或者说别名。

绑定上下文

构造出 Axios 实例后调用了自实现的 bind 函数并传入了 Axios 原型的 request 方法和 Axios 实例;bind 代码如下:


// lib/helpers/bind.jsfunction bind(fn, thisArg) {  return function wrap() {    return fn.apply(thisArg, arguments);  };}
复制代码


bind 函数返回了一个新函数 instance,执行它时会调用 fn 并将它内部的 this 指向 thisArg。从代码上下文看,这里的 fnrequest 方法,thisArgAxios 实例。

拷贝数据

接下来又将 Axios 原型与实例的数据全部拷贝至 instance 身上,代码使用了 utils.extend 方法,实现如下:


// lib/utils.jsconst extend = (a, b, thisArg, { allOwnKeys } = {}) => {  forEach(    b,    (val, key) => {      if (thisArg && isFunction(val)) {        a[key] = bind(val, thisArg);      } else {        a[key] = val;      }    },    { allOwnKeys }  );  return a;};
复制代码


主要是遍历 b 并将其中数据全部赋值给 a,如果 thisArg 存在且遍历到的值是函数则调用 bind 函数改变原方法的 this 指向。

添加 create 方法

拷贝完数据后,代码给 instance 函数添加了一个 create 方法,内部调用了 createInstance 并将配置合并后传给了它,以此来创建 axios 副本。


最后将 instance 返回就得到了我们所使用的 axios,随后还添加了一些工具,比如用于取消请求的 CancelToken 构造器,用于并发请求的 all 方法,完整列表如下:


// Axios 类axios.Axios = Axios;// 取消请求的自定义 Error 对象, 继承自 AxiosErroraxios.CanceledError = CanceledError;// CancelToken 构造器, 用于取消请求axios.CancelToken = CancelToken;// 工具函数, 判断对应的请求是否已取消axios.isCancel = isCancel;// axios 版本axios.VERSION = VERSION;// 将对象转换为 FormData 的工具函数axios.toFormData = toFormData;// 自定义的 Error 对象,继承自 Erroraxios.AxiosError = AxiosError;// CanceledError 的别名axios.Cancel = axios.CanceledError;// 并发请求axios.all = function all(promises) {  return Promise.all(promises);};// apply 的语法糖axios.spread = spread;// 判断错误对象是否是 AxiosErroraxios.isAxiosError = isAxiosError;// 合并配置的工具函数axios.mergeConfig = mergeConfig;// headers 相关的工具类axios.AxiosHeaders = AxiosHeaders;// FormData 转换为 json 的工具函数axios.formToJSON = (thing) =>  formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);// http 状态码对象axios.HttpStatusCode = HttpStatusCode;// default 指向自己axios.default = axios;
复制代码


-- end


发布于: 刚刚阅读数: 4
用户头像

yuanyxh

关注

站在巨人的肩膀上 2023-08-19 加入

web development

评论

发布
暂无评论
学习 axios 源码(一)_js_yuanyxh_InfoQ写作社区