axios是一个基于promise的http库,支持浏览器和node端,最近我在作beauty-we的api设计,研读一个成熟的http库势在必行,axios功能完整、api简洁、注释清晰,再适合不过,如今就让咱们开始吧~javascript
clone项目,打开dist文件夹,里面有axios.js和axios.min.js,看一下axios.js的第一段:html
(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种状况:vue
我比较了其余3个类库的处理(underscore/jquery/vue):java
(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 _; }); } }());
(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 ) { // ...这里是主代码部分 })
(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个库中,node
啰嗦了那么多以后,正式开始看axios啦。根据package.json里的npm run dev的指向,咱们打开lib文件夹里的axios.js,这就是全局入口了。jquery
./lib/axios.js只作了两件事:webpack
先看axios对象的建立:ios
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步开始就比较奇怪了:git
var instance = bind(Axios.prototype.request, context);
看一下bind函数:es6
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...
入口文件的分析就到此结束了。
开始研究核心代码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,真正发送请求的部分。喜欢就点个赞吧
参考文章: