资讯专栏INFORMATION COLUMN

axios源码阅读(一)

k00baa / 3340人阅读

摘要:开始研究核心代码这个类首先是构造函数看完上面的内容大家应该有点印象,上挂了和,是默认的配置,顾名思义就是拦截器,目测包含了和两种类型。喜欢就点个赞吧参考文章源代码重点难点分析源代码重点难点分析

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

准备工作

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

(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种情况:

exports和module都存在时,cmd模式;

define和define.amd都存在时,amd模式;

只有exports存在时;

既不是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只做了两件事:

创建一个axios对象

暴露一些拓展方法出去

先看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源代码重点难点分析

es6-promise源代码重点难点分析

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95851.html

相关文章

  • axios源码阅读

    摘要:响应的拦截器接收到的是对象至此,我们已经把的核心逻辑阅读完毕,从中我们也可以看到的易用性和可拓展性非常强。尤其是可拓展性,发送请求到接收响应的过程中的所有部分几乎都是可拓展的,尤其是,,留下了很多想象的空间。 为了方便使用,axios对象既能做对象使用,又能做函数使用. axios.post(/user, { firstName: Fred, lastName: Fli...

    snifes 评论0 收藏0
  • 次eggjs+axios传输multipart的纠错过程

    摘要:总所周知,的策略让每次都要发送码验证,为了方便,我在的里作了前置拦截。结果不幸从此发生最开始没有看官方文档,以为应该加在里面,又没有考虑到要上传格式的文档,所以直接结果发送的是。这很正常,阅读源码知为时会自动添加的头。不加又以上传了。 总所周知,egg的csrf策略让post每次都要发送token码验证,为了方便,我在axios的interceptor里作了前置拦截。 结果不幸从此发生...

    CarterLi 评论0 收藏0
  • Axios源码深度剖析 - AJAX新王者

    摘要:我们先来看看构造函数构造函数就是用来实现拦截器的,这个构造函数原型上有个方法。关于源码,其实是比较简单的,都是用来操作该构造函数的实例属性的。存放拦截器方法,数组内每一项都是有两个属性的对象,两个属性分别对应成功和失败后执行的函数。 Axios源码分析 - XHR篇 文章源码托管在github上,欢迎fork指正! axios 是一个基于 Promise 的http请求库,可以用在浏览...

    DangoSky 评论0 收藏0
  • sau交流学习社区--看小说的lovebook个无线端BS应用

    摘要:爱上阅读,是一款的读小说等书籍的并且阅读的应用。找了好久发现发现只有追书神器的暴露出来了,起点之类的找不到。八最后最后打个小广告源码都已在上开源,目前在逐步完善功能中。欢迎感兴趣的同学和。 loveBook loveBook爱上阅读,是一款webapp的读小说等书籍的并且阅读的应用。如果觉得可以,欢迎fork和star。 自己最近在追斗破苍穹电视剧,下班时候在地铁上总听到有人说,斗破苍...

    leeon 评论0 收藏0
  • 微豆 - Vue 2.0 实现豆瓣 Web App 教程

    摘要:微豆一个使用与重构豆瓣的项目。在中的配置代理重新启动,打开查看结果是否与直接请求豆瓣相同。更多请参考豆瓣电影文档。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 微豆 Vdo 一个使用 Vue.js 与 Material Design 重构 豆瓣 的项目。 项目网站 http://vdo.ralfz.com/ GitHub https:...

    cjie 评论0 收藏0

发表评论

0条评论

k00baa

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<