axios源碼閱讀(一)

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

  1. exports和module都存在時,cmd模式;
  2. define和define.amd都存在時,amd模式;
  3. 只有exports存在時;
  4. 既不是cmd也不是cmd模式時,掛載在傳入的宿主對象上。

我比較了其餘3個類庫的處理(underscore/jquery/vue):java

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個庫中,node

  • 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,這就是全局入口了。jquery

./lib/axios.js只作了兩件事:webpack

  1. 建立一個axios對象
  2. 暴露一些拓展方法出去

先看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...

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

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

參考文章:

相關文章
相關標籤/搜索