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,真正發送請求的部分。喜歡就點個贊吧
參考文章: