用axios和后端接口进行数据交互,那么axios内部实现原理到底是什么样的

axios源码学习

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js中进行使用。github地址

之前就粗略地阅读了axios的一些核心源码,最近闲来无事,就对axios的构建过程及重要特性的实现又进一步地去阅读,毕竟是吃饭的家伙嘛,还是要做到心里有数的。axios的常见用法几相关特性就不在这里一一罗列了,不清楚的同学可以先移步到这里。篇幅有限,node环境相关的学习,大家感兴趣的可以进一步学习。

源码目录

我们分析的源码都在/lib这个大目录下,目录如下。

  • adaapters 适配器(适配浏览器环境和node环境)
  • cancel 取消请求类(定义请求取消的类)
  • core 核心功能模块 (请求定义及实现的核心逻辑)
  • helper (辅助功能模块)
  • axiox.js (axios导出的文件,入口文件)
  • default.js (axios的默认配置文件)
  • utils (常用的工具类方法)

axios工作流程图

开局一张图,剩下全靠编。 (浏览器环境) Snipaste_2020-12-11_12-25-35.png

axios工作原理解析

我们先不关注axios的一些扩展方法,先理清axios是如何进行工作的,我们先找到入口文件axios.js

var bind = require('./helpers/bind'); // 绑定上下文函数
var Axios = require('./core/Axios'); // 核心代码入口
var mergeConfig = require('./core/mergeConfig'); // 合并对象
var defaults = require('./defaults'); // 默认配置文件

function createInstance(defaultConfig) { // 创建axios对象实例
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);
  utils.extend(instance, Axios.prototype, context);
  utils.extend(instance, context);
  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);
modules.export.default axios
复制代码
  1. 我们可以看到,我们一直使用的axios是通过createInstance这个方法返回的
  2. createInstance里的返回的axios函数,是对Axios这个核心类生成的context实例进行处理后返回的
  3. 有两个关键的工具函数extend和bind,对context实例进行处理
extend函数
function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      // 将b对象上的key value, 赋值到a对象上
      a[key] = val;
    }
  });
  return a;
}
bind函数 基本于原生js的bind方法没有区别,其实就是返回Axios.prototype.request函数
function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
  };
}
复制代码

这么一顿骚操作后,在createInstance返回的axios函数的属性上,就已经挂载了Axios原型上的方法和属性了。

核心类Axios

Axios类的实现在core/Axios.js中,

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = { // 拦截器
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Axios.prototype.request = function request(config) {
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }
  // 合并默认配置和用户自定义配置
  config = mergeConfig(this.defaults, config);
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }
  // 定义promise链
  var chain = [dispatchRequest, undefined];
  
  var promise = Promise.resolve(config);
  
  // 请求拦截器是向前插入的,所以拦截器函数执行的顺序是倒序的
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  // 相应拦截器是向后插入的,所以拦截器函数执行的顺序是正续的
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  // 当且仅但chain链的为空时
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  // 最后返回一个promise
  return promise;
};
复制代码

我们看下这个Axios实例化跟request函数做了什么事情。

  1. 首先对默认的config和自定义的config进行了合并处理。
  2. 接着定义了promise链chain数组,并且通过promise.resolve(config)生成一个新的promise,接着对请求拦截器和相应拦截器插入到chain数组中。
  3. 判断chain数组长度,如果length不等于0,不断的更新promise的值,最后返回最终的promise。

这也是为什么我们可以链式调用的axios的原因,因为最终返回的是一个promise对象。接着我们来一一对其中的重要的组成部分进行分析。

dispatchRequest

代码在core/dispatchRequest.js下,这里我们先不关注请求取消的逻辑,只看请求发送的逻辑部分

module.exports = function dispatchRequest(config) {
  // 判断请求是否取消
  throwIfCancellationRequested(config);
  // Ensure headers exist
  config.headers = config.headers || {};
  // Transform request data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );
  // Flatten headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  );
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);
    // Transform response data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );
    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);
      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }
    return Promise.reject(reason);
  });
};
复制代码

dispatchRequest函数的逻辑非常清晰明了

  1. 首先会对通过transformData对config的data、headers、transformRequest属性进行处理,这里也验证了流程图汇总,在发送xhr前,会对confgi进行transformData的处理
transform函数
var utils = require('./../utils');
module.exports = function transformData(data, headers, fns) {
  /*eslint no-param-reassign:0*/
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });

  return data;
};
复制代码

可以从代码中看出传入的fns其实是一个函数数组,循环遍历函数数组,将当前的data和headers属性当作参数传入每一个fn中,并将返回值作为新的data的值,从而达到对config.data的多次处理,从官方文档中也可以看到这样的用法

// `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data, headers) {
    // 对 data 进行任意转换处理
    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理
    return data;
  }],
复制代码
  1. 对config.headers字段进行了深度合并,接着将headers上绑定的请求方法名遍历循环删除掉
  2. 通过config.adpater来确定是node环境还是浏览器环境,从而决定是调用node.js的http模块方法还是浏览的XHR方法。在默认的配置文件中core/default.js
function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}
复制代码

通过getDefaultadapter方法,去引用不同的文件,浏览器环境的话就直接走到

adapter = require('./adapters/xhr');
复制代码
  1. 最后会对请求的返回的响应结果进行transofromData的处理,与上面处理请求参数的原理相似。

xhr

....

文章未完

因为是复制别人的文章,看完整文章,访问:面试一点通,http://www.msedt.com/infoflow/details/1053

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值