读完axios中的工具函数库后,我的收获与总结

1,491 阅读5分钟

前言

本期给大家带来的是axios中的工具函数库,里面总共有30+个或用于判断类型,或用于处理字符串,或为了兼容性而进行了二次封装的工具函数。

总体而言,阅读难度偏低,只要你有JavaScript基础,阅读起来绝对不费力。

同时阅读的收益可能会很高,因为里面大部分函数都是我们会在日常开发中用到的。

总而言之,性价比非常高。

阅读本文,你将学到:

1、javascript、nodejs调试技巧及调试工具;
2、如何学习调试axios源码;
3、如何学习优秀开源项目的代码,应用到自己的项目;
4、axios源码中实用的工具函数;

而你需要

1、javascript基础知识;

关注我不迷路:

vx: codebangbang。

掘金: 爱嘿嘿的小黑。

持续分享优质技术博客。

整体源码

如果你想自己阅读,可以直接点击我给你提供的连接,结合源码和我的分析可以更高效的完成源码的阅读哦!

axios/utils

以下是我仓库中直接拷贝过来的源码,懒得同学可以直接看我的分析。

'use strict';

var bind = require('./helpers/bind');

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

var toString = Object.prototype.toString;

/**
 * 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 Array.isArray(val);
}

/**
 * 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 a Buffer
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Buffer, otherwise false
 */
function isBuffer(val) {
  return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
    && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
}

/**
 * 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 toString.call(val) === '[object 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) && (isArrayBuffer(val.buffer));
  }
  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 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 plain Object
 *
 * @param {Object} val The value to test
 * @return {boolean} True if value is a plain Object, otherwise false
 */
function isPlainObject(val) {
  if (toString.call(val) !== '[object Object]') {
    return false;
  }

  var prototype = Object.getPrototypeOf(val);
  return prototype === null || prototype === Object.prototype;
}

/**
 * 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 toString.call(val) === '[object 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.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}

/**
 * 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'
  );
}

/**
 * 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);
      }
    }
  }
}

/**
 * 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 (isPlainObject(result[key]) && isPlainObject(val)) {
      result[key] = merge(result[key], val);
    } else if (isPlainObject(val)) {
      result[key] = merge({}, val);
    } else if (isArray(val)) {
      result[key] = val.slice();
    } else {
      result[key] = val;
    }
  }

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

/**
 * 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;
}

/**
 * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
 *
 * @param {string} content with BOM
 * @return {string} content value without BOM
 */
function stripBOM(content) {
  if (content.charCodeAt(0) === 0xFEFF) {
    content = content.slice(1);
  }
  return content;
}

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

源码分类

第一类: 类型判断

在上面的源码中几乎95%都是用于类型判断的,判断的类型包括Array,Undefined,Buffer,FormData,Object,PlainObject,ArrayBuffer,ArrayBufferView,String,Number,Date,File,Blob,Function,Stream,URLSearchParams,一共16种,这些类型有可能你并不全部认识(我就有些不认识).

不过你不用担心,因为作者对于这些类型的判断使用的技巧其实大部分就是JavaScript中的typef和Object.prototype.toString.call这两种。

我将这些用于判断类型的函数分为了三类,分别进行分析。

1.针对基本数据类型用的类型判断

基本数据类型:undefined,null,string,number,boolean,bigInt,symbol.

axios/utils是这样处理的

function isUndefined(val) {
  return typeof val === 'undefined';
}

function isString(val) {
  return typeof val === 'string';
}

function isNumber(val) {
  return typeof val === 'number';
}

仓库中只对这三种基本数据类型进行了类型判断,但其实都是用的是同一套函数,用的都是typeof这个方法。

对于剩下的那几个基本数据类型的类型判断也都是一个套路。

注意: typeof null === 'object',所以对null是不适用的,不过我们可以直接val === null来判断。

2.针对大部分复杂数据类型的类型判断

复杂数据类型其实就是指object,所以除了上面那几个基本数据类型,其余的都是复杂数据类型。

axios/utils是这样处理复杂类型的

function isArrayBuffer(val) {
  return Objet.prototype.toString.call(val) === '[object ArrayBuffer]';
}

function isFormData(val) {
  return Objet.prototype.toString.call(val) === '[object FormData]';
}

function isPlainObject(val) {
  if (Objet.prototype.toString.call(val) !== '[object Object]') {
    return false;
  }
  var prototype = Object.getPrototypeOf(val);
  return prototype === null || prototype === Object.prototype;
}

function isDate(val) {
  return Objet.prototype.toString.call(val) === '[object Date]';
}
function isFile(val) {
  return Objet.prototype.toString.call(val) === '[object File]';
}

function isBlob(val) {
  return Objet.prototype.toString.call(val) === '[object Blob]';
}
function isFunction(val) {
  return Objet.prototype.toString.call(val) === '[object Function]';
}

function isURLSearchParams(val) {
  return Objet.prototype.toString.call(val) === '[object URLSearchParams]';
}

仓库中只对这8种复杂数据类型进行了类型判断,但其实都是用的是同一套函数,用的都是Objet.prototype.toString.call这个方法。

3.针对一些比较特殊类型判断

前面一共讲了11个函数的类型判断,还有5个类型没有分析,分别是Object,Array,Buffer,ArrayBufferView,Stream

Object

function isObject(val) {
  return val !== null && typeof val === 'object'; // 只要排除null的影响,就可以用typeof来区分是否是对象了
}

Array

 function isArray(val) {
  return Array.isArray(val); // 用的是ES6新出的方法isArray
}

其实无论是Object还是Array我们都可以用Object.prototype.toString.call来判断。

我们可以重写一下这两个函数

function isObject(val) {
  return Objet.prototype.toString.call(val) === '[object Object]';
}

function isArray(val) {
  return Objet.prototype.toString.call(val) === '[object Array]';
}

效果是一样的哦。

而接下来的三个就不一样了。

Buffer


function isBuffer(val) {
  return val !== null // 判断不是 null
          && !isUndefined(val) // 判断不是 undefined
          && val.constructor !== null 判断 `val`存在构造函数,因为`Buffer`本身是一个类
          && !isUndefined(val.constructor 同上
          && typeof val.constructor.isBuffer === 'function' 最后通过自身的`isBuffer`方法判断
          && val.constructor.isBuffer(val);同上
}

这里简单介绍一下Buffer

什么是Buffer?

JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。

但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。详细可以看 官方文档更通俗易懂的解释

因为axios可以运行在浏览器和node环境中,所以内部会用到nodejs相关的知识。 ArrayBufferView

Stream

 function isStream(val) {
  return isObject(val) && isFunction(val.pipe); // isObjce和isFunction我们在上面都有提到哦
}

ArrayBufferView

 function isArrayBufferView(val) {
  var result;
  if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
    result = ArrayBuffer.isView(val);
  } else {
    result = (val) && (val.buffer) && (isArrayBuffer(val.buffer));
  }
  return result;
}

说实话,我不知道这个类型到底是干嘛,自己也比较懒,这个函数大家有兴趣自己了解一下哈。

第二类: 字符串处理

trim作用:去除字符串两边空格

function trim(str) {
  return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');// 要是有trim方法直接用否则用正则表达式处理
}

第三类: 二次封装(为了兼容性)

forEach: 将forEach和for..in...封装到一个函数中

function forEach(obj, fn) {
  if (obj === null || typeof obj === 'undefined') { // 如果传入的obj是空或undefined,啥也不返回
    return;
  }

  if (typeof obj !== 'object') {       // 如果传入的不是对象,返回包括调用者的数组
    obj = [obj];
  }

  if (isArray(obj)) {            // 如果传入的是数组, 和ES6的forEach逻辑相同
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    for (var key in obj) {       // 如果传入的是数组, 和ES6的for...in..逻辑相同
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

封装一个typeFn函数

基于上面的种种分析,我们可以自己来封装一个type函数用于判断数据类型。

const typeFn = (val) => {

let typeListObj = {};

const typeList = ["Number","Boolean","String","Null","Undefined","Array","Function","Object","RegExp","Date","Error"]

typeList.map((item) => {
    return (typeListObj[`[object ${item}]`] = item.toLowerCase());
});

if (typeof val == "null") {
    return val + "";
}

if (typeof val !== "object") {
    return typeof val
}

return  typeListObj[Object.prototype.toString.call(val)] || "object" 

};

typeFn业务逻辑分析如下

  • 首先将需要进行判断的所有复杂类型的字符串放入一个typeList数组中,并利用这个数组封装一个typeListObj对象,typeListObj对象的key为[object 大写数据类型], value为小写数据类型,最后这个typeListObj对象长这样
{
   [object Number]: "number"
   [object Boolean]: "boolean",
   [object String]: "string",
   [object Null]: "null",
   [object Undefined]: "undefined",
   [object Array]: "array",
   [object Function]: "function",
   [object Object]: "object",
   [object RegExp]: "regexp",
   [object Date]: "date",
   [object Error]: "error"
}
  • 对于传入的数据判断是否为空,如果是,直接返回"null"
  • 如果不是,再判断是否是基本数据类型,是的话直接用typeof判断并返回
  • 再然后就是复杂数据类型了,针对复杂数据类型用Object.prototype.toString.call(val),将得到的结果再去我们前面的typeListObj对象中进行匹配,找出相应的数据类型,如果没找到,默认返回object

总结

在日后的类型判断中你可以记住一下几句话

  • 基本数据类型判断用typeof,当然需要注意null这个异类

  • 引用数据类型用Object.peototype.toString方法,当然用instanceof也不是也行,不过用不好容易出现错误,比如一个数组,它可以被 instanceof 判断为 Object.

  • 最好的类型判断的方法是封装一个函数,基本数据类型用typeof判断,引用数据类型用Object.prototype.toString判断。

  • instanceof主要用于判断实例与构造函数的从属关系