axios源码阅读(一)

发布时间:2019-08-19 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了axios源码阅读(一)脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

axios是一个基于promise的http库,支持浏览器和node端,最近我在做beauty-we的api设计,研读一个成熟的http库势在必行,ax@L_126_2@功能完整、api简洁、注释清晰,再适合不过,现在就让我们开始吧~

准备工作

clone项目,打开dist文件夹,里面有axios.js和axios.min.js,看一下axios.js的第一段:

@H_512_15@(function webpackUniversalModuleDefinITion(root, factory) { if (tyPEof exports === 'object' && typeof module === 'object') { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { exports.axios = factory(); } else { root.axios = factory(); } })(this, function() {...我是主代码部分...});

最外层是一个立即执行函数(IIFE),传入参数this和构造函数factory,区分了4种情况:

  1. exports和module都存在时,cmd模式;
  2. define和define.amd都存在时,amd模式;
  3. 只有exports存在时;
  4. 既不是cmd也不是cmd模式时,挂载在传入的宿主对象上。

我比较了其他3个类库的处理(underscore/jquery/vue):

underscore

(function() {
    VAR root = typeof self == 'object' && self.self === self && self ||typeof global == 'object' && global.global === global && global ||this ||{};

    if (typeof exports != 'undefined' && !exports.nodeType) {
        if (typeof module != 'undefined' && !module.nodeType && module.exports) {
            exports = module.exports = _;
        }
        exports._ = _;
      } else {
        root._ = _;
      }

  // ....这里是主代码部分...
  if (typeof define == 'function' && define.amd) {
    define('underscore', [], function() {
      return _;
    });
  }
}());

jquery

(function( global, factory ) {
if ( typeof module === "object" && typeof module.exports === "object" ) {
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }
if ( typeof define === "function" && define.amd ) {
    define( "jquery", [], function() {
        return jQuery;
    } );
}

})(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
    // ...这里是主代码部分
})

vue


(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global.Vue = factory());
}(this, (function () {
    // ...这里是主代码部分...
}))

4个库中,

  • underscore和axios考虑了4种环境情况,vue和jquery无视了exports存在但没有module的情况(这种情况什么时候出现我不清楚,知道的人求指出);环境判断vue用了3目运算符,看起来又干净又漂亮(其实这好像并不重要,压缩后大家都一样~);对amd的支持underscore和jquery都放在了代码的尾部,打补丁的痕迹好明显;
  • jquery强调了document一定存在,否则会抛出错误,axios和vue只是传入了this,underscore则认真区分了browser的window,server端的global,其他端的this,自创了一个self统一之,这意味着后三者可以存活在更普遍的环境中(比如axios in web worker,比如weex),underscore的做法最严谨;
  • 除了underscore,其他3者都把主代码放在了IIFE的参数里,而underscore是直接写在IIFE里,我个人还是喜欢用那个叫factory的函数。

看整体

啰嗦了那么多之后,正式开始看axios啦。根据package.json里的npm run dev的指向,我们打开lib文件夹里的axios.js,这就是全局入口了。

./lib/axios.js只做了两件事:

  1. 创建一个axios对象
  2. 暴露一些拓展方法出去

先看axios对象的创建:

var axios = createinstance(defaults);

function createInstance(defaultconfig) {
  // 一切的开始,new一个实例出来
  var context = new Axios(defaultConfig);

  var instance = bind(Axios.PRototype.request, context);

  utils.extend(instance, Axios.prototype, context);

  utils.extend(instance, context);

  return instance;
}

创建实例axios用了4步,第2步开始就比较奇怪了:

var instance = bind(Axios.prototype.request, context);

看一下bind函数:

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);
  };
}

它返回一个wrap()函数,其中执行了参数1的函数,绑定了参数2的上下文,也就是说,instance是一个叫wrap的函数,它里面执行了Axios原型对象上的request方法,同时绑定了最开始创建的那个实例的上下文;
再看下一行:

utils.extend(instance, Axios.prototype, context);

看一下extend函数:

function extend(a, b, thisArg) {
  foreach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

它承担了两个任务,当参数3存在时,它循环参数2,把参数2 上的属性是函数类型的都用bind的方式挂载到参数1身上;否则的话,它实现了简单的把参数2的复制给参数1;
那么看最后一行就知道了,3-4实现的是把Axios原型上的函数属性都挂载到instance,这是第2行的一个循环版本,最后把axios实例复制给instance,形成一个真正的instance,此时,这个instance具备Axios实例的静态属性(复制来的),Axios.prototype上所有函数方法(通过bind,返回一个wrap(),里面调用Axios.prototype上的方法),instance本身还是一个wraP版本的Axios.prototype.request方法。
这时我好想问一个:为什么这么做。。
思考好久找到突破口,就是最奇怪的最后一点,axios是一个wrap函数,包含了request,这其实是因为axios支持的两种调用方式:
axios({config}).then()axios.get('url').then()
前者确定了axios自身作为一个函数的特性,同时,它还需要具有get/post等一堆方法,于是我翻到的commit最初的版本是:

var defaultInstance = new Axios();

var axios = module.exports = bind(Axios.prototype.request, defaultInstance);

这里还少了把defaultInstance上的静态属性复制给axios的过程,当时Axios上的静态属性还很少,只有defaults,被手动挂载给axios:axios.defaults=defaults;
随着Axios添加了InterceptorManager拦截器,进化成了第二版本:

var defaultInstance = new Axios();

var axios = module.exports = bind(Axios.prototype.request, defaultInstance);

axios.defaults=defaults;
axios.interceptors = defaultInstance.interceptors;

但是有人发现了一个bug,因为axios自带create方法,可以自定义一个实例,而create内写的却是极度简陋的:

axios.create = function (defaultConfig) {
  return new Axios(defaultConfig);
};

大家发现axios.create出来的和axios比较后api少很多啊,于是终极版本的构建函数createInstance诞生了:

function createInstance(defaultConfig) {
  // 一切的开始,new一个实例出来,然而考虑到它不是最终对象,只能称之为上下文
  var context = new Axios(defaultConfig);
// 这是最后返回的实例,本身是一个request函数
  var instance = bind(Axios.prototype.request, context);
  // 为了拓展性,一口气把原型对象上所有的方法都拷一份
  utils.extend(instance, Axios.prototype, context);

  // 把实例的静态属性复制过来
  utils.extend(instance, context);

  return instance;
}

终于创建完axios对象,接下来是暴露一些api出去:

// Factory for creating new instances
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread =function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
}
axios.Cancel...
axios.CancelToken...
axios.isCancel...

入口文件的分析就到此结束了。

core Axios

开始研究核心代码Axios这个类;
首先是构造函数:

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

看完上面的内容大家应该有点印象,axios上挂了defaults和interceptors,defaults是默认的config配置,interceptors顾名思义就是拦截器,目测包含了request和response两种类型。
接下来看个简单的部分:

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

axios把7种请求都转向了request方法,根据是否要带data写了2个循环,看起来还挺简洁的。
接下来看最核心的request代码,这里的处理很巧妙很优雅:


Axios.prototype.request = function request(config) {
    // ...我是很多config处理...此时不关心

    // 创建了一个事件数组,第一位的是发送请求的动作,这是默认状态
    var chain = [dispatchRequest, undefined];
    // 创建了一个promise对象
  var promise = Promise.resolve(config);
//   循环request拦截器,有的话就添加到chain的头部
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
// 循环response拦截器,有的话就添加到chain的尾部
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
//   利用while循环,执行完所有事件
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift()); 
} 
// 返回最终的promise对象
    return promise; 
}

核心axios对象到此已看完,下一节计划分析dispatchRequest---adapter,真正发送请求的部分。喜欢就点个赞吧

参考文章

脚本宝典总结

以上是脚本宝典为你收集整理的axios源码阅读(一)全部内容,希望文章能够帮你解决axios源码阅读(一)所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。