逐行解析Axios原始碼

賽博朋克的傑洛特發表於2019-08-11

原始碼目錄結構


# ? lib
# |——  ? adapters // axios主要使用的請求方法
# |——  |——  ? http.js  // axios中node端使用的請求函式
# |——  |——  ? xhr.js  // axios中瀏覽器端使用的請求函式
# |——  ? cancel
# |——  |——  ? Cancel.js // 定義了,取消請求返回的資訊結構
# |——  |——  ? CancelToken.js // 定義了用於取消請求的主要方法
# |——  |——  ? isCancel.js // 判斷是否是取消請求的資訊
# |——  ? core
# |——  |——  ? Axios.js // Axios類
# |——  |——  ? dispatchRequest.js // 發起請求的地方 
# |——  |——  ? InterceptorManager.js // InterceptorManager類,攔截器類
# |——  |——  ? mergeConfig.js // 合併配置項
# |——  |——  ? settle.js // 根據請求狀態,處理Promise
# |——  |——  ? createError.js // 生成指定的error
# |——  |——  ? enhanceError.js // 指定error物件的toJSON方法
# |——  |——  ? transformData.js // 使用default.js中transformRequest和transformResponse對響應以及請求進行格式化
# |——  ? helpers
# |——  |——  ? bind.js // 工具函式
# |——  |——  ? parseHeaders.js // 將getAllResponseHeaders返回的header資訊轉化為物件
# |——  |——  ? buildURL.js // 將params引數
# |——  |——  ? cookies.js // 封裝了讀取,寫入,刪除cookies的方法
# |——  |——  ? isURLSameOrigin.js // 檢測當前的url與請求的url是否同源
# |——  |——  ? normalizeHeaderName.js // 對物件屬性名的進行格式化,刪除,新建符合大小寫規範的屬性
# |——  |——  ? combineURLs.js // 組合baseurl
# |——  |——  ? isAbsoluteURL.js // 判斷是否為絕對路徑(指的://或//開頭的為絕對路徑)
# |——  ? axios.js
# |——  ? defaults.js // axios中預設配置
# |——  ? utils.js // 一些工具方法
# |——  |——  ⏹ isFormData // 判斷是否是formData物件
# |——  |——  ⏹ isStandardBrowserEnv // 判斷當前環境是否為標準瀏覽器環境
# |——  |——  ⏹ isUndefined // 判斷是否為undefined
# |——  |——  ⏹ merge
# |——  |——  ⏹ isURLSearchParams // 判斷是否為URLSearchParams物件
複製程式碼

前言

本文主要關注axios中主流程的原始碼,對於一些工具函式的實現會略過。還請見諒。如果文章中有錯誤的地方,還請及時指出。

請求流程概覽

下面是axios原始碼中發起一個請求時程式碼大致的流程

QQ20190808-172442.png

原始碼逐行分析

/lib/cancel/CancelToken.js

CancelToken.js中定義了取消axios請求的相關行為的程式碼。但CancelToken.source返回的取消請求的cancel方法,使用的前提,是需要將CancelToken.source返回token的,結合到具體的請求的config中才能正常使用。

如何在axios中使用取消請求的功能?

我在看axios原始碼之前,甚至並不知道axios可以發出的請求,所以我們先來了解下如何在axios取消一個請求。下面是一個例子?

// axios用於取消請求的類
const CancelToken = axios.CancelToken
// source方法會返回一個物件,物件包含
// {
  // token, 新增到請求的config,用於標識請求
  // cancel, 呼叫cancel方法取消請求
// }
const source = CancelToken.source()

axios.get('/info', {
  cancelToken: source.token
}).catch(function(error) {
  if (axios.isCancel(error)) {
    console.log('取消請求的錯誤')
  } else {
    // 其他錯誤
  }
})

// 呼叫source.cancel可以取消axios.get('/info')的請求
source.cancel('取消請求')
複製程式碼

原始碼

var Cancel = require('./Cancel');

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;

  // 建立一個Promise
  // 在呼叫cancel函式前該promise會一直處於pending狀態
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;

  executor(function cancel(message) {
    // 判斷是否已經取消請求了
    if (token.reason) {
      return;
    }

    // 建立取消請求的資訊,並將資訊新增到例項的reason屬性上
    token.reason = new Cancel(message);
  
    // 結束this.promise的pending狀態
    // 將this.promise狀態設定為resolve
    resolvePromise(token.reason);
  });
}

// 判斷該請求是否已經被取消的方法
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;
複製程式碼

? 看到這裡,我們還是無法瞭解axios是如何取消一個請求的。因為單獨使用CancelToken.source返回的cancel是無法取消一個請求的,我們需要結合xhr.js中的程式碼來理解。


// /lib/adapters/xhr.js

request.open()

// ...省略

// 如果配置了cancelToken選項
if (config.cancelToken) {
  
  // 對CancelToken中建立的Promise新增成功的回撥
  // 當呼叫CancelToken.source暴露的cancel函式時,回撥會被觸發
  config.cancelToken.promise.then(function onCanceled(cancel) {

    if (!request) {
      return;
    }

    // 取消xhr請求
    request.abort();
    
    // 將axios返回的promise,置為reject態
    reject(cancel);

    request = null;
  });
}

// ...省略

request.send()

複製程式碼

想必大家看到這裡,對axios中如何請求有了一個大致的瞭解。我們來總結一下,我們通過CancelToken,建立了一個額外的PromiseA,並將PromiseA掛載到config中,同時將該PromiseA的resolve方法暴露出去。我們在呼叫send方法前(傳送請求前)新增對PromiseA的狀態進行監聽,當PromiseA的狀態被修改,我們會在PromiseA的callback中取消請求,並且將axios返回的PromiseB的狀態置為reject。從而達到取消請求的目的

/lib/adapters/xhr.js

xhr.js匯出的xhrAdapter方法是axios在瀏覽器環境下使用的預設請求方法。我們可以在配置中使用adapter配置項對預設請求方法進行替換。

原始碼


module.exports = function xhrAdapter(config) {

  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    // 判斷是否是FormData物件, 如果是, 刪除header的Content-Type欄位,讓瀏覽器自動設定Content-Type欄位
    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type'];
    }

    // 建立xtr物件
    var request = new XMLHttpRequest();

    // 設定http請求頭中的Authorization欄位
    // 關於Authorization欄位
    // 更多內容參考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Authorization
    if (config.auth) {
      var username = config.auth.username || '';
      var password = config.auth.password || '';
      // 使用btoa方法base64編碼username和password
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    // 初始化請求方法
    // open(method: 請求的http方法, url: 請求的url地址, 是否支援非同步)
    request.open(
      config.method.toUpperCase(),
      buildURL(config.url, config.params, config.paramsSerializer),
      true
    );

    // 設定超時時間
    request.timeout = config.timeout;

    // 監聽readyState狀態的變化,當readyState狀態為4的時候,表示ajax請求成功
    request.onreadystatechange = function handleLoad() {
      if (!request || request.readyState !== 4) {
        return;
      }

      // request.status響應的數字狀態碼,在完成請求前數字狀態碼等於0
      // 如果request.status出錯返回的也是0,但是file協議除外,status等於0也是一個成功的請求
      // 更多內容請參考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/status
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
        return;
      }

      // getAllResponseHeaders方法會返回所有的響應頭
      // 更多內容請參考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/getAllResponseHeaders
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;

      // 如果沒有設定資料響應型別(預設為“json”)或者responseType設定為text時,獲取request.responseText值否則是獲取request.response
      // responseType是一個列舉型別,手動設定返回資料的型別 更多請參考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseType
      // responseText是全部後端的返回資料為純文字的值 更多請參考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseText
      // response為正文,response的型別取決於responseType 更多請參考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/response
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;

      var response = {
        data: responseData, // 響應正文
        status: request.status, // 響應狀態
        statusText: request.statusText, // 響應狀態的文字資訊
        headers: responseHeaders, // 響應頭
        config: config,
        request: request
      };

      // status >= 200 && status < 300 resolve
      // 否則reject
      settle(resolve, reject, response);

      request = null;
    };

    // ajax中斷時觸發
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }

      // 丟擲Request aborted錯誤
      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      request = null;
    };

    // ajax失敗時觸發
    request.onerror = function handleError() {
      // 丟擲Network Error錯誤
      reject(createError('Network Error', config, null, request));

      request = null;
    };

    // ajax請求超時時呼叫
    request.ontimeout = function handleTimeout() {
      // 丟擲 timeout錯誤
      reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',
        request));

      request = null;
    };

    // 判斷當前是為標準瀏覽器環境,如果是,新增xsrf頭
    // 什麼是xsrf header? xsrf header是用來防禦CSRF攻擊
    // 原理是服務端生成一個XSRF-TOKEN,並儲存到瀏覽器的cookie中,在每次請求中ajax都會將XSRF-TOKEN設定到request header中
    // 伺服器會比較cookie中的XSRF-TOKEN與header中XSRF-TOKEN是否一致
    // 根據同源策略,非同源的網站無法讀取修改本源的網站cookie,避免了偽造cookie
    if (utils.isStandardBrowserEnv()) {
      var cookies = require('./../helpers/cookies');

      // withCredentials設定跨域請求中是否應該使用cookie 更多請參考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials
      // (設定了withCredentials為true或者是同源請求)並且設定xsrfCookieName
      var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
      // 讀取cookie中XSRF-TOKEN
        cookies.read(config.xsrfCookieName) :
        undefined;

      if (xsrfValue) {
        // 在request header中設定XSRF-TOKEN
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

    // setRequestHeader是用來設定請求頭部的方法
    if ('setRequestHeader' in request) {
      // 將config中配置的requestHeaders,迴圈設定到請求頭上
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
          delete requestHeaders[key];
        } else {
          request.setRequestHeader(key, val);
        }
      });
    }

    // 設定xhr物件的withCredentials屬性,是否允許cookie進行跨域請求
    if (config.withCredentials) {
      request.withCredentials = true;
    }

    // 設定xhr物件的responseType屬性
    if (config.responseType) {
      try {
        request.responseType = config.responseType;
      } catch (e) {
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }

    // 下載進度
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // 上傳進度
    // request.upload XMLHttpRequest.upload 屬性返回一個 XMLHttpRequestUpload物件,用來表示上傳的進度
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    if (config.cancelToken) {
      // 取消請求,在介紹/lib/cancel/CancelToken.js中以及介紹,這裡不在贅述
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }
        request.abort();
        reject(cancel);
        request = null;
      });
    }

    if (requestData === undefined) {
      requestData = null;
    }

    // 傳送http請求
    request.send(requestData);
  });
};
複製程式碼

/lib/core/dispatchRequest.js

dispatchRequest.js檔案是axios原始碼中實際呼叫請求的地方。

原始碼

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');

// 判斷請求是否已被取消,如果已經被取消,丟擲已取消
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // 如果包含baseUrl, 並且不是config.url絕對路徑,組合baseUrl以及config.url
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    // 組合baseURL與url形成完整的請求路徑
    config.url = combineURLs(config.baseURL, config.url);
  }

  config.headers = config.headers || {};

  // 使用/lib/defaults.js中的transformRequest方法,對config.headers和config.data進行格式化
  // 比如將headers中的Accept,Content-Type統一處理成大寫
  // 比如如果請求正文是一個Object會格式化為JSON字串,並新增application/json;charset=utf-8的Content-Type
  // 等一系列操作
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 合併不同配置的headers,config.headers的配置優先順序更高
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // 刪除headers中的method屬性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // 如果config配置了adapter,使用config中配置adapter的替代預設的請求方法
  var adapter = config.adapter || defaults.adapter;

  // 使用adapter方法發起請求(adapter根據瀏覽器環境或者Node環境會有不同)
  return adapter(config).then(
    // 請求正確返回的回撥
    function onAdapterResolution(response) {
      // 判斷是否以及取消了請求,如果取消了請求丟擲以取消
      throwIfCancellationRequested(config);

      // 使用/lib/defaults.js中的transformResponse方法,對伺服器返回的資料進行格式化
      // 例如,使用JSON.parse對響應正文進行解析
      response.data = transformData(
        response.data,
        response.headers,
        config.transformResponse
      );

      return response;
    },
    // 請求失敗的回撥
    function onAdapterRejection(reason) {
      if (!isCancel(reason)) {
        throwIfCancellationRequested(config);

        if (reason && reason.response) {
          reason.response.data = transformData(
            reason.response.data,
            reason.response.headers,
            config.transformResponse
          );
        }
      }
      return Promise.reject(reason);
    }
  );
};
複製程式碼

/lib/core/InterceptorManager.js

InterceptorManager.js檔案中定義了axios攔截器類。包含了攔截器的新增,刪除,迴圈攔截器。無論是響應攔截器還是請求攔截器,都是使用陣列進行儲存的。

var utils = require('./../utils');
 
// 攔截器類
function InterceptorManager() {
  // handlers陣列用來儲存攔截器
  this.handlers = [];
}

// 新增攔截器,use方法接收兩個引數,成功的回撥以及失敗的回撥
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    // 成功的回撥
    fulfilled: fulfilled,
    // 失敗的回撥
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// 根據id(索引),刪除例項handlers屬性中攔截器
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// 迴圈攔截器
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

module.exports = InterceptorManager;
複製程式碼

/lib/core/Axios.js

QQ20190808-160016.png

Axios.js檔案中定義了Axios例項上的request,get,post,delete方法。get,post,delete等方法均是基於Axios.prototype.request的封裝?。在Axios.prototype.request中會依次執行請求攔截器,dispatchRequest(實際發起),響應攔截器。整體的流程如?上圖所示。

原始碼

var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');

function Axios(instanceConfig) {
  // Axios的配置
  this.defaults = instanceConfig;
  // 攔截器
  this.interceptors = {
    request: new InterceptorManager(), // 請求攔截器
    response: new InterceptorManager() // 響應攔截器
  };
}

Axios.prototype.request = function request(config) {

  // 如果config是一個字串,把字串當作請求的url地址
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  // 合併配置
  config = mergeConfig(this.defaults, config);
  // 如果沒有指定請求方法,使用get方法
  config.method = config.method ? config.method.toLowerCase() : 'get';

  var promise = Promise.resolve(config);

  // 將請求攔截器,和響應攔截器,以及實際的請求(dispatchRequest)的方法組合成陣列,類似如下的結構
  // [請求攔截器1success, 請求攔截器1error, 請求攔截器2success, 請求攔截器2error, dispatchRequest, undefined, 響應攔截器1success, 響應攔截器1error]
  var chain = [dispatchRequest, undefined];
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  
  // 開始執行整個請求流程(請求攔截器->dispatchRequest->響應攔截器)
  // 流程可以理解為上圖⬆️
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};

// 基於Axios.prototype.request封裝其他方法
// 將delete,get,head,options,post,put,patch新增到Axios.prototype的原型鏈上
// Axios.prototype.delete =
// Axios.prototype.get =
// Axios.prototype.head =
// Axios.prototype.options =
// ...
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;
複製程式碼

/lib/defaults.js

defaults.js檔案中配置了,axios預設的請求頭、不同的環境下axios預設使用的請求方法、格式化請求正文的方法,格式化響應正文方法等內容

原始碼

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');

// 預設Content-Type
var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};

// 設定ContentType,在沒有設定的情況下
function setContentTypeIfUnset(headers, value) {
  if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value;
  }
}

// 根據當前環境,獲取預設的請求方法
function getDefaultAdapter() {
  var adapter;
  // 判斷當前環境是否存在process物件
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // node環境
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {
    // 瀏覽器環境
    adapter = require('./adapters/xhr');
  }
  return adapter;
}

var defaults = {
  // 預設的請求方法
  adapter: getDefaultAdapter(),

  // 格式化請求requestData,這會請求傳送前使用
  transformRequest: [
    function transformRequest(data, headers) {
      // 格式化header屬性名,將header中不標準的屬性名,格式化為Accept屬性名
      normalizeHeaderName(headers, 'Accept');
      // 格式化header屬性名,將header中不標準的屬性名,格式化為Content-Type屬性名
      normalizeHeaderName(headers, 'Content-Type');

      if (utils.isFormData(data) ||
        utils.isArrayBuffer(data) ||
        utils.isBuffer(data) ||
        utils.isStream(data) ||
        utils.isFile(data) ||
        utils.isBlob(data)
      ) {
        return data;
      }
      if (utils.isArrayBufferView(data)) {
        return data.buffer;
      }

      // URLSearchParams提供了一些用來處理URL查詢字串介面
      // 如果是URLSearchParams物件
      if (utils.isURLSearchParams(data)) {
        // Content-Type設定為application/x-www-form-urlencoded
        // application/x-www-form-urlencoded,資料被編碼成以&分隔的鍵值對
        setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
        return data.toString();
      }

      // 如果是物件
      if (utils.isObject(data)) {
        // Content-Type設定為application/json
        setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
        // 將請求正文格式化為JSON字串,並返回
        return JSON.stringify(data);
      }

      return data;
    }
  ],

  // 格式化響應resposeData,這會響應接受後使用
  transformResponse: [
    function transformResponse(data) {
      if (typeof data === 'string') {
        try {
          data = JSON.parse(data);
        } catch (e) { /* Ignore */ }
      }
      return data;
    }
  ],

  // 預設超時時間
  timeout: 0,

  // xsrf設定的cookie的key
  xsrfCookieName: 'XSRF-TOKEN',
  // xsrf設定header的key
  xsrfHeaderName: 'X-XSRF-TOKEN',

  maxContentLength: -1,

  // 驗證請求的狀態
  // 在處理請求的Promise會被使用
  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  }
};

defaults.headers = {
  // 通用的HTTP欄位
  // Accept告知客戶端可以處理的型別
  common: {
    'Accept': 'application/json, text/plain, */*'
  }
};

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

// 為post,put,patch請求設定預設的Content-Type
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

module.exports = defaults;
複製程式碼

/lib/axios.js

axios.js檔案是axios工具庫的入口方法,在axios.js

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

// 建立axios例項
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);

  // 更改Axios.prototype.request的this,執行context例項
  // instance等於Axios.prototype.request方法
  var instance = bind(Axios.prototype.request, context);

  // 將Axios.prototype,context上的屬性合併到instance
  // instance.get = Axios.prototype.get
  // instance.defaults = context.defaults
  // ...
  utils.extend(instance, Axios.prototype, context); 
  utils.extend(instance, context);

  return instance;
}

// axios會直接對使用者暴露一個axios.request的方法,所以我們在使用axios的時候可以這樣使用。不需要new一個axios的例項
// import axios from 'axios'
// axios.get('/info')
var axios = createInstance(defaults);

axios.Axios = Axios;

// axios.create可以根據使用者自定義的config生成一個新的axios例項
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

axios.all = function all(promises) {
  return Promise.all(promises);
};

axios.spread = require('./helpers/spread');

module.exports = axios;

module.exports.default = axios;
複製程式碼

相關文章