【源码阅读】axios的46个工具函数


theme: juejin
highlight: a11y-dark

我正在参与掘金会员专属活动-源码共读第一期,点击参与

继上次阅读Vue2的工具函数后,这次来阅读axios的工具函数,来看看两个库的工具函数有什么不同或者相同的地方。

这次直接使用github1s来阅读源码,因为是工具函数,调试可以直接在浏览器控制台,这样方便很多,地址:https://github1s.com/axios/axios/blob/v1.x/lib/utils.js

所有工具函数

还是老样子,先看看axios的工具函数有哪些,先心里有个印象,然后再逐个分析。

直接拉到最下面,可以看到axios的工具函数都是统一导出的:

export default {
  isArray, // 判断是否是数组
  isArrayBuffer, // 判断是否是 ArrayBuffer
  isBuffer, // 判断是否是 Buffer
  isFormData, // 判断是否是 FormData
  isArrayBufferView, // 判断是否是 ArrayBufferView
  isString, // 判断是否是字符串
  isNumber, // 判断是否是数字
  isBoolean, // 判断是否是布尔值
  isObject, // 判断是否是对象
  isPlainObject, // 判断是否是纯对象
  isUndefined, // 判断是否是 undefined
  isDate, // 判断是否是日期
  isFile, // 判断是否是文件
  isBlob, // 判断是否是 Blob
  isRegExp, // 判断是否是正则
  isFunction, // 判断是否是函数
  isStream, // 判断是否是 Stream
  isURLSearchParams, // 判断是否是 URLSearchParams
  isTypedArray, // 判断是否是 TypedArray
  isFileList, // 判断是否是 FileList
  forEach, // 遍历对象
  merge, // 合并对象
  extend, // 扩展对象
  trim, // 去除字符串两边的空格
  stripBOM, // 去除字符串的 BOM
  inherits, // 继承
  toFlatObject, // 将对象转换为扁平对象
  kindOf, // 获取对象的类型
  kindOfTest, // 判断对象的类型
  endsWith,// 判断字符串是否以指定的字符串结尾
  toArray, // 将类数组转换为数组
  forEachEntry, // 遍历对象的键值对
  matchAll, // 匹配所有的字符串
  isHTMLForm, // 判断是否是 HTMLForm
  hasOwnProperty, // 判断对象是否有指定的属性
  hasOwnProp: hasOwnProperty,  // 判断对象是否有指定的属性
  reduceDescriptors, // 降低描述符
  freezeMethods, // 冻结方法
  toObjectSet, // 将数组或者字符串转换为类似`Set`的对象
  toCamelCase, // 将字符串转换为驼峰命名
  noop, // 空函数
  toFiniteNumber, // 将字符串转换为有限数字
  findKey, // 查找对象的键
  global: _global, // 全局对象
  isContextDefined, // 判断上下文是否定义
  toJSONObject // 将字符串转换为 JSON 对象
};
  • isArray:判断是否是数组
  • isArrayBuffer:判断是否是 ArrayBuffer
  • isBuffer:判断是否是 Buffer
  • isFormData:判断是否是 FormData
  • isArrayBufferView:判断是否是 ArrayBufferView
  • isString:判断是否是字符串
  • isNumber:判断是否是数字
  • isBoolean:判断是否是布尔值
  • isObject:判断是否是对象
  • isPlainObject:判断是否是纯对象
  • isUndefined:判断是否是 undefined
  • isDate:判断是否是日期
  • isFile:判断是否是文件
  • isBlob:判断是否是 Blob
  • isRegExp:判断是否是正则
  • isFunction:判断是否是函数
  • isStream:判断是否是 Stream
  • isURLSearchParams:判断是否是 URLSearchParams
  • isTypedArray:判断是否是 TypedArray
  • isFileList:判断是否是 FileList
  • forEach:遍历对象
  • merge:合并对象
  • extend:扩展对象
  • trim:去除字符串两边的空格
  • stripBOM:去除字符串的 BOM
  • inherits:继承
  • toFlatObject:将对象转换为扁平对象
  • kindOf:获取对象的类型
  • kindOfTest:判断对象的类型
  • endsWith,// 判断字符串是否以指定的字符串结尾
  • toArray:将类数组转换为数组
  • forEachEntry:遍历对象的键值对
  • matchAll:匹配所有的字符串
  • isHTMLForm:判断是否是 HTMLForm
  • hasOwnProperty:判断对象是否有指定的属性
  • hasOwnProp: hasOwnProperty, :判断对象是否有指定的属性
  • reduceDescriptors:降低描述符
  • freezeMethods:冻结方法
  • toObjectSet:将数组或者字符串转换为类似Set的对象
  • toCamelCase:将字符串转换为驼峰命名
  • noop:空函数
  • toFiniteNumber:将字符串转换为有限数字
  • findKey:查找对象的键
  • global: _global:全局对象
  • isContextDefined:判断上下文是否定义
  • toJSONObject:将字符串转换为 JSON 对象

一共有46个工具函数,比Vue2的工具函数多了很多,同时也发现有很多同名的工具函数,例如:

  1. isArray
  2. isRegExp
  3. isFunction
  4. isObject
  5. isPlainObject
  6. extend
  7. noop

别看这么多,其中对于类型判断的函数只有少数几个需要关注,其他的都是使用同样的方式来实现的,如下:

  • isArrayBuffer
  • isBuffer
  • isString
  • isNumber
  • isUndefined
  • isDate
  • isFile
  • isBlob
  • isRegExp
  • isFunction
  • isStream
  • isURLSearchParams
  • isFileList
  • isHTMLForm

一共14个,这些函数都是通过kindOf来实现的,上面这些相同的会在源码阅读中移除。

正式阅读

这里的kindOfkindOfTest优先阅读,因为这两个函数是用来判断类型的,上面移除的14个函数都是通过这两个函数来实现的,所以这两个函数非常重要也非常经典。

kindOf

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

const {toString} = Object.prototype;

const kindOf = (cache => thing => {
    const str = toString.call(thing);
    return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
})(Object.create(null));

kindOf函数用于获取对象的类型,比如kindOf({})返回的是object

它是一个立即执行函数,返回一个函数,这个函数接收一个参数,这个参数就是要获取类型的对象;

抛开立即执行函数不说,kindOf函数的核心代码只有两行:

const {toString} = Object.prototype;

const kindOf = (thing) => {
    const str = toString.call(thing);
    return str.slice(8, -1).toLowerCase();
};

这里的处理其实和Vue2中类型判断的处理是一样的,都是通过Object.prototype.toString来获取对象的类型;

kindOf函数的返回值是一个字符串,这个字符串就是对象的类型,不过这里使用了一个缓存,这样就不用每次都去执行str.slice(8, -1).toLowerCase()了;

kindOfTest

const kindOfTest = (type) => {
  type = type.toLowerCase();
  return (thing) => kindOf(thing) === type
}

kindOfTest函数用于判断对象的类型,同样也是一个高阶函数;

它接收一个参数,这个参数就是要判断的类型,返回一个函数,这个函数接收一个参数,这个参数就是要判断的对象;

返回的函数通过函数柯里化的特性,将type参数固定下来,这样就可以通过kindOfTest('object')来判断对象的类型了;

也就是axios中的大多数类型判断都是通过kindOfTest函数来实现的;

isArrayBuffer

const {toString} = Object.prototype;

const kindOf = (cache => thing => {
    const str = toString.call(thing);
    return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
})(Object.create(null));

const kindOfTest = (type) => {
    type = type.toLowerCase();
    return (thing) => kindOf(thing) === type
}

/**
 * Determine if a value is an ArrayBuffer
 *
 * @param {*} val The value to test
 *
 * @returns {boolean} True if value is an ArrayBuffer, otherwise false
 */
const isArrayBuffer = kindOfTest('ArrayBuffer');

这个就是使用kindOfTest函数来判断对象的类型,isArrayBuffer函数接收一个参数,这个参数就是要判断的对象,返回一个布尔值,表示这个对象是否是ArrayBuffer类型;

上面过滤掉的14个函数都是通过这种方式来实现的,这里就不一一列举了;

这是一个非常经典的高阶函数应用模式,函数柯里化的应用,可以看看:✨从柯里化讲起,一网打尽 JavaScript 重要的高阶函数

isArray

/**
 * Determine if a value is an Array
 *
 * @param {Object} val The value to test
 *
 * @returns {boolean} True if value is an Array, otherwise false
 */
const {isArray} = Array;

直接使用解构赋值,将Array.isArray赋值给isArray,和Vue2的实现方式一样;

isBuffer

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

/**
 * Determine if a value is a Function
 *
 * @param {*} val The value to test
 * @returns {boolean} True if value is a Function, otherwise false
 */
const isFunction = typeOfTest('function');

/**
 * Determine if a value is a Buffer
 *
 * @param {*} 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)
    && isFunction(val.constructor.isBuffer) && val.constructor.isBuffer(val);
}

isBuffer函数的实现依赖了isUndefinedisFunction函数,而isUndefinedisFunction函数的实现依赖了typeOfTest函数;

这个在上面的kindOfTest函数中已经介绍过了,所以这里就一口气了解三个函数,当然主角还是isBuffer函数;

isBuffer函数的实现是判断val是否是Buffer,这里的判断需要满足以下条件:

  • val不是null
  • val不是undefined
  • valconstructor不是null
  • valconstructor不是undefined
  • valconstructorisBuffer方法是一个函数
  • valconstructorisBuffer方法返回true

总体来说还是很复杂的,这里的主角是valconstructorisBuffer方法,这个方法是Buffer对象的静态方法;

参考:

isFormData

/**
 * Determine if a value is a FormData
 *
 * @param {*} thing The value to test
 *
 * @returns {boolean} True if value is an FormData, otherwise false
 */
const isFormData = (thing) => {
  const pattern = '[object FormData]';
  return thing && (
    (typeof FormData === 'function' && thing instanceof FormData) ||
    toString.call(thing) === pattern ||
    (isFunction(thing.toString) && thing.toString() === pattern)
  );
}

isFormData函数的实现很简单,就是判断thing是否是FormData,这里的判断需要满足以下条件:

  • thing不是null
  • FormData是一个函数
  • thingFormData的实例
  • thing的类型是[object FormData]
  • thingtoString方法是一个函数
  • thingtoString方法返回[object FormData]
  • thingtoString方法返回[object FormData]

边界限定条件比较多,可以是多种情况,最主要的还是thingFormData的实例;

参考:

isArrayBufferView

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

isArrayBufferView函数的实现依赖了isArrayBuffer函数,同时还使用了ArrayBuffer.isView方法;

参考:

isString

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

isString函数的实现依赖了typeOfTest函数;

isNumber

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

isNumber函数的实现依赖了typeOfTest函数;

isBoolean

/**
 * Determine if a value is a Boolean
 *
 * @param {*} thing The value to test
 * @returns {boolean} True if value is a Boolean, otherwise false
 */
const isBoolean = thing => thing === true || thing === false;

isBoolean函数的实现很简单,就是判断thing是否是true或者false

这里用===判断,不会出现隐式类型转换的问题;

isObject

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

isObject函数的实现和Vue2的实现一样,就是判断thing是否是null或者typeof thing是否是object

isPlainObject

const {getPrototypeOf} = Object;

/**
 * Determine if a value is a plain Object
 *
 * @param {*} val The value to test
 *
 * @returns {boolean} True if value is a plain Object, otherwise false
 */
const isPlainObject = (val) => {
  if (kindOf(val) !== 'object') {
    return false;
  }

  const prototype = getPrototypeOf(val);
  return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in val) && !(Symbol.iterator in val);
};

isPlainObject函数的实现依赖了kindOf函数,同时还使用了Object.getPrototypeOf方法;

这里的判断对比于Vue2的实现严格了很多,还加上了原型、Symbol.toStringTagSymbol.iterator的判断;

参考:

isUndefined

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

isUndefined函数的实现依赖了typeOfTest函数;

isStream

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

isStream函数的实现依赖了isObjectisFunction函数;

isTypedArray

/**
 * Checking if the Uint8Array exists and if it does, it returns a function that checks if the
 * thing passed in is an instance of Uint8Array
 *
 * @param {TypedArray}
 *
 * @returns {Array}
 */
// eslint-disable-next-line func-names
const isTypedArray = (TypedArray => {
  // eslint-disable-next-line func-names
  return thing => {
    return TypedArray && thing instanceof TypedArray;
  };
})(typeof Uint8Array !== 'undefined' && getPrototypeOf(Uint8Array));

isTypedArray函数的实现是一个立即执行函数,返回一个函数,也是一个高阶函数;

这里的判断是为了兼容IEIE中没有Uint8Array,所以这里判断了Uint8Array是否存在,如果存在,就返回一个函数,这个函数的作用是判断传入的参数是否是Uint8Array的实例;

forEach

/**
 * 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
 *
 * @param {Boolean} [allOwnKeys = false]
 * @returns {any}
 */
function forEach(obj, fn, {allOwnKeys = false} = {}) {
  // Don't bother if no value provided
  if (obj === null || typeof obj === 'undefined') {
    return;
  }

  let i;
  let l;

  // 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 (i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj);
    }
  } else {
    // Iterate over object keys
    const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
    const len = keys.length;
    let key;

    for (i = 0; i < len; i++) {
      key = keys[i];
      fn.call(null, obj[key], key, obj);
    }
  }
}

手动实现了一个forEach函数,这个函数可以遍历对象和数组,如果是对象,就遍历对象的属性,如果是数组,就遍历数组的元素;

看着很长,内部其实还是for循环,只是对obj的类型做了判断,如果是对象就获取对象的key,然后遍历;

参考:

merge

function findKey(obj, key) {
    key = key.toLowerCase();
    const keys = Object.keys(obj);
    let i = keys.length;
    let _key;
    while (i-- > 0) {
        _key = keys[i];
        if (key === _key.toLowerCase()) {
            return _key;
        }
    }
    return null;
}

const _global = typeof self === "undefined" ? typeof global === "undefined" ? this : global : self;

const isContextDefined = (context) => !isUndefined(context) && context !== _global;

/**
 * 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:
 *
 * 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, ... */) {
    const {caseless} = isContextDefined(this) && this || {};
    const result = {};
    const assignValue = (val, key) => {
        const targetKey = caseless && findKey(result, key) || key;
        if (isPlainObject(result[targetKey]) && isPlainObject(val)) {
            result[targetKey] = merge(result[targetKey], val);
        } else if (isPlainObject(val)) {
            result[targetKey] = merge({}, val);
        } else if (isArray(val)) {
            result[targetKey] = val.slice();
        } else {
            result[targetKey] = val;
        }
    }

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

merge函数用于合并对象,如果对象中有相同的属性,后面的对象会覆盖前面的对象;

这里采用的是递归的方式,如果属性值是对象,就递归调用merge函数,如果是数组,就复制一份;

extend

/**
 * 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
 *
 * @param {Boolean} [allOwnKeys]
 * @returns {Object} The resulting value of object a
 */
const extend = (a, b, thisArg, {allOwnKeys}= {}) => {
  forEach(b, (val, key) => {
    if (thisArg && isFunction(val)) {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  }, {allOwnKeys});
  return a;
}

extend函数用于扩展对象,如果属性值是函数,就绑定this

trim

/**
 * 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
 */
const trim = (str) => str.trim ?
  str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');

trim函数用于去除字符串两边的空格,如果浏览器支持trim函数,就直接调用,否则就用正则表达式去除;

stripBOM

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

stripBOM函数用于去除字符串的 BOM,如果字符串的第一个字符是0xFEFF,就去除;

可以自己查一下字符串BOM是什么,目前我找到的资料讲解的不是很清楚;

大概的意思就是,在一些编码的文件中,会在文件的开头添加一个特殊的字符,而这些字符是不可见的,所以叫做BOM

如果不去除BOM,就会导致一些问题,比如文件打不开,或者部分文件操作会出错;

inherits

/**
 * Inherit the prototype methods from one constructor into another
 * @param {function} constructor
 * @param {function} superConstructor
 * @param {object} [props]
 * @param {object} [descriptors]
 *
 * @returns {void}
 */
const inherits = (constructor, superConstructor, props, descriptors) => {
  constructor.prototype = Object.create(superConstructor.prototype, descriptors);
  constructor.prototype.constructor = constructor;
  Object.defineProperty(constructor, 'super', {
    value: superConstructor.prototype
  });
  props && Object.assign(constructor.prototype, props);
}

inherits函数用于继承,constructor是子类,superConstructor是父类;

constructor.prototype = Object.create(superConstructor.prototype, descriptors);这一行代码就是继承父类的原型;

constructor.prototype.constructor = constructor;这一行代码是为了保证子类的constructor属性指向子类;

Object.defineProperty(constructor, 'super', {value: superConstructor.prototype});这一行代码是为了保证子类的super属性指向父类的原型;

props && Object.assign(constructor.prototype, props);这一行代码是为了扩展子类的原型;

toFlatObject

/**
 * Resolve object with deep prototype chain to a flat object
 * @param {Object} sourceObj source object
 * @param {Object} [destObj]
 * @param {Function|Boolean} [filter]
 * @param {Function} [propFilter]
 *
 * @returns {Object}
 */
const toFlatObject = (sourceObj, destObj, filter, propFilter) => {
  let props;
  let i;
  let prop;
  const merged = {};

  destObj = destObj || {};
  // eslint-disable-next-line no-eq-null,eqeqeq
  if (sourceObj == null) return destObj;

  do {
    props = Object.getOwnPropertyNames(sourceObj);
    i = props.length;
    while (i-- > 0) {
      prop = props[i];
      if ((!propFilter || propFilter(prop, sourceObj, destObj)) && !merged[prop]) {
        destObj[prop] = sourceObj[prop];
        merged[prop] = true;
      }
    }
    sourceObj = filter !== false && getPrototypeOf(sourceObj);
  } while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype);

  return destObj;
}

toFlatObject函数用于将对象转换为扁平对象,也就是将对象的原型链上的属性都拷贝到对象上;

props = Object.getOwnPropertyNames(sourceObj);这一行代码是获取对象的所有属性,包括不可枚举的属性;

destObj[prop] = sourceObj[prop];这一行代码是将对象的属性拷贝到destObj上;

sourceObj = filter !== false && getPrototypeOf(sourceObj);这一行代码是获取对象的原型;

endsWith

/**
 * Determines whether a string ends with the characters of a specified string
 *
 * @param {String} str
 * @param {String} searchString
 * @param {Number} [position= 0]
 *
 * @returns {boolean}
 */
const endsWith = (str, searchString, position) => {
  str = String(str);
  if (position === undefined || position > str.length) {
    position = str.length;
  }
  position -= searchString.length;
  const lastIndex = str.indexOf(searchString, position);
  return lastIndex !== -1 && lastIndex === position;
}

endsWith函数用于判断字符串是否以指定的字符串结尾;

它接收三个参数,第一个参数是要判断的字符串,第二个参数是要判断的字符串,第三个参数是开始判断的位置;

position参数是可选的,如果没有传递,那么就默认为字符串的长度,超过字符串的长度就取字符串的长度;

position参数减去searchString的长度,就是开始判断的位置;

然后通过indexOf方法来判断searchString是否在str中,如果在,那么就返回true,否则返回false

toArray

/**
 * Returns new array from array like object or null if failed
 *
 * @param {*} [thing]
 *
 * @returns {?Array}
 */
const toArray = (thing) => {
  if (!thing) return null;
  if (isArray(thing)) return thing;
  let i = thing.length;
  if (!isNumber(i)) return null;
  const arr = new Array(i);
  while (i-- > 0) {
    arr[i] = thing[i];
  }
  return arr;
}

toArray函数用于将类数组转换为数组;

不同于Vue2中的toArray函数,axios中的明显更严谨一些,但是效果却是一样的;

Vue2中使用了各种隐式转换,而axios中则是使用了严谨的判断,对比下来Vue2中的toArray函数更加简洁;

Vue2toArray函数:

export function toArray(list, start) {
    start = start || 0
    let i = list.length - start
    const ret = new Array(i)
    while (i--) {
        ret[i] = list[i + start]
    }
    return ret
}

forEachEntry

/**
 * For each entry in the object, call the function with the key and value.
 *
 * @param {Object<any, any>} obj - The object to iterate over.
 * @param {Function} fn - The function to call for each entry.
 *
 * @returns {void}
 */
const forEachEntry = (obj, fn) => {
  const generator = obj && obj[Symbol.iterator];

  const iterator = generator.call(obj);

  let result;

  while ((result = iterator.next()) && !result.done) {
    const pair = result.value;
    fn.call(obj, pair[0], pair[1]);
  }
}

forEachEntry函数用于遍历对象的键值对;

它接收两个参数,第一个参数是要遍历的对象,第二个参数是回调函数;

forEachEntry函数通过Symbol.iterator来获取对象的迭代器,然后通过while循环来遍历对象的键值对;

参考:

matchAll

/**
 * It takes a regular expression and a string, and returns an array of all the matches
 *
 * @param {string} regExp - The regular expression to match against.
 * @param {string} str - The string to search.
 *
 * @returns {Array<boolean>}
 */
const matchAll = (regExp, str) => {
  let matches;
  const arr = [];

  while ((matches = regExp.exec(str)) !== null) {
    arr.push(matches);
  }

  return arr;
}

matchAll函数用于匹配所有的字符串;

它接收两个参数,第一个参数是正则表达式,第二个参数是要匹配的字符串;

matchAll函数通过while循环来匹配所有的字符串,然后将匹配到的字符串放入数组中;

参考:

hasOwnProperty

/* Creating a function that will check if an object has a property. */
const hasOwnProperty = (
    ({hasOwnProperty}) => (obj, prop) => hasOwnProperty.call(obj, prop)
)(Object.prototype);

hasOwnProperty函数用于判断对象是否有指定的属性;

是一个高阶函数,还是一个立即执行函数,主要是通过Object.prototype.hasOwnProperty来判断对象是否有指定的属性;

hasOwnProp: hasOwnProperty, // 判断对象是否有指定的属性

没有代码,只是一个别名,注释表示:避免 ESLint 无原型内置检测的别名

reduceDescriptors

const reduceDescriptors = (obj, reducer) => {
  const descriptors = Object.getOwnPropertyDescriptors(obj);
  const reducedDescriptors = {};

  forEach(descriptors, (descriptor, name) => {
    if (reducer(descriptor, name, obj) !== false) {
      reducedDescriptors[name] = descriptor;
    }
  });

  Object.defineProperties(obj, reducedDescriptors);
}

reduceDescriptors函数用于降低描述符;

它接收两个参数,第一个参数是要降低的对象,第二个参数是回调函数;

reduceDescriptors函数通过Object.getOwnPropertyDescriptors来获取对象的所有属性描述符,然后通过forEach函数来遍历对象的所有属性描述符;

参考:

freezeMethods

/**
 * Makes all methods read-only
 * @param {Object} obj
 */

const freezeMethods = (obj) => {
  reduceDescriptors(obj, (descriptor, name) => {
    // skip restricted props in strict mode
    if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) {
      return false;
    }

    const value = obj[name];

    if (!isFunction(value)) return;

    descriptor.enumerable = false;

    if ('writable' in descriptor) {
      descriptor.writable = false;
      return;
    }

    if (!descriptor.set) {
      descriptor.set = () => {
        throw Error('Can not rewrite read-only method '' + name + ''');
      };
    }
  });
}

freezeMethods函数用于冻结方法;

它接收一个参数,是要冻结的对象,freezeMethods函数通过reduceDescriptors函数来降低对象的所有属性描述符;

toObjectSet

const toObjectSet = (arrayOrString, delimiter) => {
  const obj = {};

  const define = (arr) => {
    arr.forEach(value => {
      obj[value] = true;
    });
  }

  isArray(arrayOrString) ? define(arrayOrString) : define(String(arrayOrString).split(delimiter));

  return obj;
}

toObjectSet函数用于将数组或者字符串转换为类似Set的对象;

它接收两个参数,第一个参数是要转换的数组或者字符串,第二个参数是分隔符;

toObjectSet函数通过isArray函数来判断第一个参数是否是数组,如果是数组,就直接遍历数组,将数组的每一项作为obj的属性,值为true

如果第一个参数不是数组,就将第一个参数转换为字符串,然后通过split函数来分割字符串,将分割后的每一项作为obj的属性,值为true

toCamelCase

const toCamelCase = str => {
  return str.toLowerCase().replace(/[_-\s]([a-z\d])(\w*)/g,
    function replacer(m, p1, p2) {
      return p1.toUpperCase() + p2;
    }
  );
};

toCamelCase函数用于将字符串转换为驼峰命名;

Vue2中也有类似的函数:camelize

两者的实现的原理都是相同的,都是通过正则表达式来匹配字符串,然后通过回调函数来替换匹配到的字符串;

不同的是正则不一样,Vue2中的正则是/-(\w)/gaxios中的正则是/[_-\s]([a-z\d])(\w*)/g

可以看到Vue2中的正则只匹配了-,而axios中的正则匹配了_-\s,考虑的边界情况更多;

noop

const noop = () => {};

空函数,和Vue2中的noop函数一样;

toFiniteNumber

const toFiniteNumber = (value, defaultValue) => {
  value = +value;
  return Number.isFinite(value) ? value : defaultValue;
}

toFiniteNumber函数用于将字符串转换为有限数字;

它接收两个参数,第一个参数是要转换的字符串,第二个参数是默认值;

使用的是+运算符将字符串隐式转换为数字,然后通过Number.isFinite函数来判断是否是有限数字,如果是有限数字,就返回转换后的数字,否则返回默认值;

findKey

function findKey(obj, key) {
  key = key.toLowerCase();
  const keys = Object.keys(obj);
  let i = keys.length;
  let _key;
  while (i-- > 0) {
    _key = keys[i];
    if (key === _key.toLowerCase()) {
      return _key;
    }
  }
  return null;
}

findKey函数用于查找对象的键;

它接收两个参数,第一个参数是要查找的对象,第二个参数是要查找的键;

findKey函数通过Object.keys函数来获取对象的所有键,然后通过while循环来遍历这些键,将键转换为小写,然后和要查找的键进行比较,如果相等,就返回这个键,否则返回null

global: _global

const _global = typeof self === "undefined" ? typeof global === "undefined" ? this : global : self;

_global变量用于存储全局对象;

它通过typeof运算符来判断selfglobalthis是否存在,如果存在,就将它们赋值给_global

参考:

isContextDefined

const isContextDefined = (context) => !isUndefined(context) && context !== _global;

isContextDefined函数用于判断上下文是否定义;

它接收一个参数,就是要判断的上下文;

toJSONObject

const toJSONObject = (obj) => {
  const stack = new Array(10);

  const visit = (source, i) => {

    if (isObject(source)) {
      if (stack.indexOf(source) >= 0) {
        return;
      }

      if(!('toJSON' in source)) {
        stack[i] = source;
        const target = isArray(source) ? [] : {};

        forEach(source, (value, key) => {
          const reducedValue = visit(value, i + 1);
          !isUndefined(reducedValue) && (target[key] = reducedValue);
        });

        stack[i] = undefined;

        return target;
      }
    }

    return source;
  }

  return visit(obj, 0);
}

toJSONObject函数用于将对象转换为 JSON 对象;

它接收一个参数,就是要转换的对象;

toJSONObject函数通过visit函数来实现对象的转换;

visit函数接收两个参数,第一个参数是要转换的对象,第二个参数是栈的索引;

visit函数首先判断对象是否是对象,如果是对象,就判断栈中是否存在这个对象,如果存在,就返回;

然后判断对象是否有toJSON方法,如果没有,就将对象添加到栈中,然后创建一个新的对象,如果对象是数组,就创建一个空数组,否则创建一个空对象;

然后通过forEach函数来遍历对象的所有键,然后通过visit函数来递归遍历对象的值,如果值不是undefined,就将值添加到新的对象中;

最后将对象从栈中移除,然后返回新的对象;

对比 Vue2

vue2

  • vue2中的工具函数写的都非常简洁
  • vue2中对于类型判断使用了多种方式
  • vue2中使用的es6的语法比较少
  • vue2中的工具函数导出都是一个函数一个导出

axios

  • axios中的工具函数写的比较复杂
  • axios中对于类型判断只使用了一种方式
  • axios中使用的es6的语法比较多
  • axios中的工具函数导出都是一个对象一个导出
  • axios中的工具函数对于类型的种类判断较多
  • axios中的工具函数主要集中在数据的处理
  • axios中的工具函数函数签名较全面

这里没必要说优缺点,因为这些都是个人的看法,不要因为写axios的点比较多就认为axios的工具函数写的比较好,这里只是为了对比vue2axios的工具函数,看看他们的不同之处。

对于vue2的工具函数来说,它的侧重点是针对vue2的,所以会比较关注vue系统运行过程中的处理,对于数据类型种类这一块就没axios这么全面;

对于axios的工具函数来说,它的侧重点是针对网络请求,而且还是跨平台的,所以会比较关注数据的处理,对于数据类型种类这一块就比vue2这么全面;

总结

axios的工具函数使用了很多高阶函数来处理,整体对于闭包、高阶函数、函数柯里化等都有一定的应用;

axios的工具函数对于数据类型种类非常全面,囊括浏览器到node的大多数数据类型;

axios的工具函数对于函数签名非常全面,对于函数的参数类型、返回值类型都有非常详细的描述,写的非常规范,这个值得我们学习;

主要还是高阶函数的应用,这个在vue2中也有一定的应用,但是axios的应用更加明显和全面,就是看着有点扣脑阔,但是这个对于个人的提升还是很有帮助的。

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容