axios源碼系列(一) --- 目錄結構和工具函數

前言

這應該是一個大多數都經常使用的請求庫,由於它能夠支持多種配置,跨平臺實現,返回promise進行鏈式調用.徹底過一遍源碼能夠提高本身對請求庫的理解知識node

axios源碼系列(一) --- 目錄結構和工具函數
axios源碼系列(二) --- 適配器內部
axios源碼系列(三) --- 默認配置和取消請求
axios源碼系列(四) --- Axios和dispatchRequest與攔截器react

什麼是 axios?

Axios 是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中ios

特性

  • 從瀏覽器中建立 XMLHttpRequests
  • 從 node.js 建立 http 請求
  • 支持 Promise API
  • 攔截請求和響應
  • 轉換請求數據和響應數據
  • 取消請求
  • 自動轉換 JSON 數據
  • 客戶端支持防護 XSRF

源碼目錄結構

├── /cancel/                 # 定義取消功能實現
│ ├── Cancel.js                  # 請求被取消後拋出的對象,構造函數實現
│ ├── isCancel.js                # 返回是否已經取消的布爾值
│ └── CancelToken.js             # 用來請求取消操做的對象,構造函數實現
├── /core/                   # 主要的核心功能實現
│ ├── Axios.js                   # axios的構造函數
│ ├── buildFullPath.js           # 返回基礎地址和請求地址的合併URL
│ ├── createError.js             # 建立指定錯誤對象的構造函數
│ ├── dispatchRequest.js         # 使用配置的適配器發送請求到服務器
│ ├── enhanceError.js            # 修改指定錯誤對象的信息
│ ├── InterceptorManager.js      # 實現攔截器的構造函數
│ ├── mergeConfig.js             # 合併配置
│ ├── transformData.js           # 轉換數據
│ └── settle.js                  # 根據http響應狀態,改變Promise的狀態
├── /helpers/                # 一些輔助方法
│ ├── bind.js                    # bind實現方法
│ ├── buildUR.js                 # URL末尾追加參數
│ ├── combineURLs.js             # 合併URL
│ ├── cookies.js                 # cookie操做
│ ├── deprecatedMethod.js        # 警告已廢棄請求方式
│ ├── isAbsoluteURL.js           # 返回是否絕對路徑的布爾值
│ ├── isURLSameOrigin.js         # 返回是否同源的布爾值
│ ├── normalizeHeaderName.js     # 標準化對比替換請求頭
│ ├── parseHeaders.js            # 將請求頭解析成對象
│ └── spread.js                  # 用於調用函數和展開參數數組的語法糖
├── /adapters/               # 定義請求的適配器 xhr、http
│ ├── http.js                    # nodejs默認適配器
│ └── xhr.js                     # 瀏覽器默認適配器
├── axios.js                 # 默認入口
├── defaults.js              # 默認配置 
└── utils.js                 # 工具函數

工具函數

這是axios的工具函數,我我的習慣是先看工具函數,由於這些通常會充斥到整個庫,因此得先有個底知道有什麼能夠用的工具git

axios/lib/utils.js

var bind = require('./helpers/bind');
var isBuffer = require('is-buffer');

/*global toString:true*/

// utils is a library of generic helper functions non-specific to axios

var toString = Object.prototype.toString;

引入了上面的bind函數和buffer檢測庫,和對象原型鏈上的toString方法,咱們看看裏面的bind是怎麼樣的github

axios/lib/helpers/bind.js

module.exports = 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);
  };
};

bind的簡單實現,沒疑問
源碼地址web

/**
 * Determine if a value is an Array
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an Array, otherwise false
 */
function isArray(val) {
  return toString.call(val) === '[object Array]';
}

/**
 * Determine if a value is an ArrayBuffer
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an ArrayBuffer, otherwise false
 */
function isArrayBuffer(val) {
  return toString.call(val) === '[object ArrayBuffer]';
}

/**
 * Determine if a value is a FormData
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an FormData, otherwise false
 */
function isFormData(val) {
  return (typeof FormData !== 'undefined') && (val instanceof FormData);
}

/**
 * Determine if a value is a view on an ArrayBuffer
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false
 */
function isArrayBufferView(val) {
  var result;
  if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
    result = ArrayBuffer.isView(val);
  } else {
    result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer);
  }
  return result;
}

/**
 * Determine if a value is a String
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a String, otherwise false
 */
function isString(val) {
  return typeof val === 'string';
}

/**
 * Determine if a value is a Number
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Number, otherwise false
 */
function isNumber(val) {
  return typeof val === 'number';
}

/**
 * Determine if a value is undefined
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if the value is undefined, otherwise false
 */
function isUndefined(val) {
  return typeof val === 'undefined';
}

/**
 * Determine if a value is an Object
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an Object, otherwise false
 */
function isObject(val) {
  return val !== null && typeof val === 'object';
}

/**
 * Determine if a value is a Date
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Date, otherwise false
 */
function isDate(val) {
  return toString.call(val) === '[object Date]';
}

/**
 * Determine if a value is a File
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a File, otherwise false
 */
function isFile(val) {
  return toString.call(val) === '[object File]';
}

/**
 * Determine if a value is a Blob
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Blob, otherwise false
 */
function isBlob(val) {
  return toString.call(val) === '[object Blob]';
}

/**
 * Determine if a value is a Function
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Function, otherwise false
 */
function isFunction(val) {
  return toString.call(val) === '[object Function]';
}

/**
 * Determine if a value is a Stream
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Stream, otherwise false
 */
function isStream(val) {
  return isObject(val) && isFunction(val.pipe);
}

/**
 * Determine if a value is a URLSearchParams object
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a URLSearchParams object, otherwise false
 */
function isURLSearchParams(val) {
  return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
}

/**
 * Trim excess whitespace off the beginning and end of a string
 *
 * @param {String} str The String to trim
 * @returns {String} The String freed of excess whitespace
 */
function trim(str) {
  return str.replace(/^\s*/, '').replace(/\s*$/, '');
}

/**
 * Determine if we're running in a standard browser environment
 *
 * This allows axios to run in a web worker, and react-native.
 * Both environments support XMLHttpRequest, but not fully standard globals.
 *
 * web workers:
 *  typeof window -> undefined
 *  typeof document -> undefined
 *
 * react-native:
 *  navigator.product -> 'ReactNative'
 * nativescript
 *  navigator.product -> 'NativeScript' or 'NS'
 */
function isStandardBrowserEnv() {
  if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
                                           navigator.product === 'NativeScript' ||
                                           navigator.product === 'NS')) {
    return false;
  }
  return (
    typeof window !== 'undefined' &&
    typeof document !== 'undefined'
  );
}

都是些利用基礎語法的特性檢測是否對應的數據類型axios

/**
 * Iterate over an Array or an Object invoking a function for each item.
 *
 * If `obj` is an Array callback will be called passing
 * the value, index, and complete array for each item.
 *
 * If 'obj' is an Object callback will be called passing
 * the value, key, and complete object for each property.
 *
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn) {
  // Don't bother if no value provided
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  // Force an array if not already something iterable
  if (typeof obj !== 'object') {
    /*eslint no-param-reassign:0*/
    obj = [obj];
  }

  if (isArray(obj)) {
    // Iterate over array values
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

校驗參數類型分別根據可迭代對象或數組作遍歷,傳遞每一個參數到指定方法執行,若是參數是對象只會遍歷實例自己的屬性.segmentfault

/**
 * Accepts varargs expecting each argument to be an object, then
 * immutably merges the properties of each object and returns result.
 *
 * When multiple objects contain the same key the later object in
 * the arguments list will take precedence.
 *
 * Example:
 *
 * ```js
 * var result = merge({foo: 123}, {foo: 456});
 * console.log(result.foo); // outputs 456
 * ```
 *
 * @param {Object} obj1 Object to merge
 * @returns {Object} Result of all merge properties
 */
function merge(/* obj1, obj2, obj3, ... */) {
  var result = {};
  function assignValue(val, key) {
    if (typeof result[key] === 'object' && typeof val === 'object') {
      result[key] = merge(result[key], val);
    } else {
      result[key] = val;
    }
  }
  // 將result和當前值作比較
  for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  return result;
}

接收多個參數對象,合併全部對象並返回結果,當多個對象包含相同的key時,後者會覆蓋前者.其中的react-native

forEach(arguments[i], assignValue);
--------------等價於-------------------
for (var key in arguments[i]) {
  if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
    assignValue.call(null, arguments[i][key], key, arguments[i]);
  }
}
--------------等價於-------------------
for (var key in arguments[i]) {
  if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
    if (typeof result[key] === 'object' && typeof arguments[i][key] === 'object') {
      result[key] = merge(result[key], arguments[i][key]);
    } else {
      result[key] = val;
    }
  }
}

利用遞歸合併每層對象數組

/**
 * Function equal to merge with the difference being that no reference
 * to original objects is kept.
 *
 * @see merge
 * @param {Object} obj1 Object to merge
 * @returns {Object} Result of all merge properties
 */
function deepMerge(/* obj1, obj2, obj3, ... */) {
  var result = {};
  function assignValue(val, key) {
    if (typeof result[key] === 'object' && typeof val === 'object') {
      result[key] = deepMerge(result[key], val);
    } else if (typeof val === 'object') {
      result[key] = deepMerge({}, val);
    } else {
      result[key] = val;
    }
  }

  for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  return result;
}

merge相比,它只有在typeof result[key] === 'object' && typeof val === 'object'的時候纔會進行合併不然直接賦值,而deepMerge只要assignValue的入參爲對象則必定合併.

/**
 * Extends object a by mutably adding to it the properties of object b.
 *
 * @param {Object} a The object to be extended
 * @param {Object} b The object to copy properties from
 * @param {Object} thisArg The object to bind function to
 * @return {Object} The resulting value of object a
 */
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;
}

向某個對象添加另外一個對象的屬性的擴展函數,而且指定其中function綁定指向

module.exports = {
  isArray: isArray,
  isArrayBuffer: isArrayBuffer,
  isBuffer: isBuffer,
  isFormData: isFormData,
  isArrayBufferView: isArrayBufferView,
  isString: isString,
  isNumber: isNumber,
  isObject: isObject,
  isUndefined: isUndefined,
  isDate: isDate,
  isFile: isFile,
  isBlob: isBlob,
  isFunction: isFunction,
  isStream: isStream,
  isURLSearchParams: isURLSearchParams,
  isStandardBrowserEnv: isStandardBrowserEnv,
  forEach: forEach,
  merge: merge,
  deepMerge: deepMerge,
  extend: extend,
  trim: trim
};

以上爲當前源碼導出的全部工具函數

源碼地址

相關文章
相關標籤/搜索