当前位置:   article > 正文

axios源码阅读(一)

axios.js (function webpackuniversalmoduledefinition(root, factory)

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

准备工作

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

  1. (function webpackUniversalModuleDefinition(root, factory) {
  2. if (typeof exports === 'object' && typeof module === 'object') {
  3. module.exports = factory();
  4. } else if (typeof define === 'function' && define.amd) {
  5. define([], factory);
  6. } else if (typeof exports === 'object') {
  7. exports.axios = factory();
  8. } else {
  9. root.axios = factory();
  10. }
  11. })(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
  1. (function() {
  2. var root = typeof self == 'object' && self.self === self && self ||typeof global == 'object' && global.global === global && global ||this ||{};
  3. if (typeof exports != 'undefined' && !exports.nodeType) {
  4. if (typeof module != 'undefined' && !module.nodeType && module.exports) {
  5. exports = module.exports = _;
  6. }
  7. exports._ = _;
  8. } else {
  9. root._ = _;
  10. }
  11. // ....这里是主代码部分...
  12. if (typeof define == 'function' && define.amd) {
  13. define('underscore', [], function() {
  14. return _;
  15. });
  16. }
  17. }());
jquery
  1. (function( global, factory ) {
  2. if ( typeof module === "object" && typeof module.exports === "object" ) {
  3. module.exports = global.document ?
  4. factory( global, true ) :
  5. function( w ) {
  6. if ( !w.document ) {
  7. throw new Error( "jQuery requires a window with a document" );
  8. }
  9. return factory( w );
  10. };
  11. } else {
  12. factory( global );
  13. }
  14. if ( typeof define === "function" && define.amd ) {
  15. define( "jquery", [], function() {
  16. return jQuery;
  17. } );
  18. }
  19. })(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
  20. // ...这里是主代码部分
  21. })
vue
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global.Vue = factory());
  5. }(this, (function () {
  6. // ...这里是主代码部分...
  7. }))

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对象的创建:

  1. var axios = createInstance(defaults);
  2. function createInstance(defaultConfig) {
  3. // 一切的开始,new一个实例出来
  4. var context = new Axios(defaultConfig);
  5. var instance = bind(Axios.prototype.request, context);
  6. utils.extend(instance, Axios.prototype, context);
  7. utils.extend(instance, context);
  8. return instance;
  9. }

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

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

看一下bind函数:

  1. function bind(fn, thisArg) {
  2. return function wrap() {
  3. var args = new Array(arguments.length);
  4. for (var i = 0; i < args.length; i++) {
  5. args[i] = arguments[i];
  6. }
  7. return fn.apply(thisArg, args);
  8. };
  9. }

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

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

看一下extend函数:

  1. function extend(a, b, thisArg) {
  2. forEach(b, function assignValue(val, key) {
  3. if (thisArg && typeof val === 'function') {
  4. a[key] = bind(val, thisArg);
  5. } else {
  6. a[key] = val;
  7. }
  8. });
  9. return a;
  10. }

它承担了两个任务,当参数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最初的版本是:

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

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

  1. var defaultInstance = new Axios();
  2. var axios = module.exports = bind(Axios.prototype.request, defaultInstance);
  3. axios.defaults=defaults;
  4. axios.interceptors = defaultInstance.interceptors;

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

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

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

  1. function createInstance(defaultConfig) {
  2. // 一切的开始,new一个实例出来,然而考虑到它不是最终对象,只能称之为上下文
  3. var context = new Axios(defaultConfig);
  4. // 这是最后返回的实例,本身是一个request函数
  5. var instance = bind(Axios.prototype.request, context);
  6. // 为了拓展性,一口气把原型对象上所有的方法都拷一份
  7. utils.extend(instance, Axios.prototype, context);
  8. // 把实例的静态属性复制过来
  9. utils.extend(instance, context);
  10. return instance;
  11. }

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

  1. // Factory for creating new instances
  2. axios.create = function create(instanceConfig) {
  3. return createInstance(mergeConfig(axios.defaults, instanceConfig));
  4. };
  5. // Expose Axios class to allow class inheritance
  6. axios.Axios = Axios;
  7. axios.all = function all(promises) {
  8. return Promise.all(promises);
  9. };
  10. axios.spread =function spread(callback) {
  11. return function wrap(arr) {
  12. return callback.apply(null, arr);
  13. };
  14. }
  15. axios.Cancel...
  16. axios.CancelToken...
  17. axios.isCancel...

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

core Axios

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

  1. function Axios(instanceConfig) {
  2. this.defaults = instanceConfig;
  3. this.interceptors = {
  4. request: new InterceptorManager(),
  5. response: new InterceptorManager()
  6. };
  7. }

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

  1. utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  2. /*eslint func-names:0*/
  3. Axios.prototype[method] = function(url, config) {
  4. return this.request(utils.merge(config || {}, {
  5. method: method,
  6. url: url
  7. }));
  8. };
  9. });
  10. utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  11. /*eslint func-names:0*/
  12. Axios.prototype[method] = function(url, data, config) {
  13. return this.request(utils.merge(config || {}, {
  14. method: method,
  15. url: url,
  16. data: data
  17. }));
  18. };
  19. });

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

  1. Axios.prototype.request = function request(config) {
  2. // ...我是很多config处理...此时不关心
  3. // 创建了一个事件数组,第一位的是发送请求的动作,这是默认状态
  4. var chain = [dispatchRequest, undefined];
  5. // 创建了一个promise对象
  6. var promise = Promise.resolve(config);
  7. // 循环request拦截器,有的话就添加到chain的头部
  8. this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
  9. chain.unshift(interceptor.fulfilled, interceptor.rejected);
  10. });
  11. // 循环response拦截器,有的话就添加到chain的尾部
  12. this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
  13. chain.push(interceptor.fulfilled, interceptor.rejected);
  14. });
  15. // 利用while循环,执行完所有事件
  16. while (chain.length) {
  17. promise = promise.then(chain.shift(), chain.shift());
  18. }
  19. // 返回最终的promise对象
  20. return promise;
  21. }

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

参考文章:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/748199
推荐阅读
相关标签
  

闽ICP备14008679号