【源码共读】第19期 | axios 工具函数

1. 前言

3. 源码工具函数

3.1 类型判断

类型判断主要通过Object.prototype.toString.calltypeof方法进行判断。

通用的类型判断工具函数:

js 复制代码
const {toString} = Object.prototype;

// 使用typeof进行对比
const typeOfTest = type => thing => typeof thing === type;

// 定义了一个自执行函数,用于创建一个缓存对象 `cache`。
// 该缓存对象用于存储已经判断过的对象类型,以提高性能。在函数内部,通过 `Object.prototype.toString.call(thing)` 获取给定变量 `thing` 的类型字符串,并将其转换为小写形式进行存储。
//'[object Number]'=> 'Number' => 'number

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

常用类型判断:

js 复制代码
const isUndefined = typeOfTest('undefined');
const isString = typeOfTest('string');
const isFunction = typeOfTest('function');
const {isArray} = Array; // 使用数组的方法
const isNumber = typeOfTest('number');
const isObject = (thing) => thing !== null && typeof thing === 'object';
const isBoolean = thing => thing === true || thing === false;
const isDate = kindOfTest('Date');
const isFile = kindOfTest('File');
const isBlob = kindOfTest('Blob');
const isFileList = kindOfTest('FileList');
const isStream = (val) => isObject(val) && isFunction(val.pipe);
const isArrayBuffer = kindOfTest('ArrayBuffer');
const isURLSearchParams = kindOfTest('URLSearchParams'); 
const isHTMLForm = kindOfTest('HTMLFormElement');
const isRegExp = kindOfTest('RegExp');
const isAsyncFn = kindOfTest('AsyncFunction');
const isThenable = (thing) =>
  thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch);

3.2 isPlainObject 纯对象

判断是否为纯对象,指的是使用对象字面量 {} 或者通过 new Object() 创建的对象,而不是通过其他构造函数创建的对象(比如数组、函数等)。

js 复制代码
// 静态方法返回指定对象的原型
const {getPrototypeOf} = Object;

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);
}
  1. 使用 kindOf 函数判断 val 的类型是否为 object
  2. 然后获取 val 的原型对象,并分别与 nullObject.prototype 进行比较。如果原型对象等于 null,或者等于 Object.prototype,或者其上一级原型对象为 null,则继续判断。
  3. 判断给定对象 val 是否具有 Symbol.toStringTag 属性,并且不具有 Symbol.iterator 属性。
    1. (Symbol.toStringTag in val) 判断给定对象 val 是否具有 Symbol.toStringTag 属性。Symbol.toStringTag 属性是用来定制对象在调用 Object.prototype.toString() 方法时返回的字符串标签。如果 val 具有 Symbol.toStringTag 属性,则表示它可能是一个特定类型的对象,而不仅仅是一个纯对象。
    2. !(Symbol.iterator in val) 判断给定对象 val 是否不具有 Symbol.iterator 属性。Symbol.iterator 属性是用来定义对象的默认迭代器方法。通常,只有实现了迭代协议的对象才会具有 Symbol.iterator 属性。

3.3 isBuffer

判断是否是buffer,它是用来处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个Buffer 类,该类用来创建一个专门存放二进制数据的缓存区

js 复制代码
function isBuffer(val) {
  return val !== null 
          && !isUndefined(val) 
          && val.constructor !== null 
          && !isUndefined(val.constructor)
          && typeof val.constructor.isBuffer === 'function' 
          && val.constructor.isBuffer(val);
}
  1. 判断 val 不为 nullundefined
  2. val.constructor !== null:检查 val 的构造函数不为 null,以避免使用了 null 构造出的对象
  3. !isUndefined(val.constructor):确保构造函数存在
  4. 使用 typeof 操作符检查 val.constructor.isBuffer 的类型是否为函数
  5. 调用构造函数自身的isBuffer 方法进行判断

3.4 isFormData

判断是否是formDataFormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send() 方法发送出去。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。

js 复制代码
const isFormData = (thing) => {
  let kind;
  return thing && (
    (typeof FormData === 'function' && thing instanceof FormData) || 
    (isFunction(thing.append) && 
    ((kind = kindOf(thing)) === 'formdata' ||
     (kind === 'object' && 
     isFunction(thing.toString) && 
     thing.toString() === '[object FormData]') )
    )
  )
}
  1. 判断是否为 nullundefined
  2. 判断是否存在 FormData 构造函数,并且 thing 是该构造函数的实例
  3. 如果第一种判断不成立,它将进一步检查 thing 是否具有 append 方法,并且满足以下条件之一:
    • kindOf(thing) 返回值为 'formdata',类型是 formdata
    • 或者 kindOf(thing) 返回值为 'object',并且 thing 具有 toString 方法,且调用 thing.toString() 返回值等于 '[object FormData]'

3.5 isArrayBufferView

ArrayBufferView 是 JavaScript 中的一个抽象类型,用于表示与 ArrayBuffer 相关的视图。ArrayBuffer 是一种用于在内存中存储二进制数据的对象,而 ArrayBufferView 则充当了对 ArrayBuffer 中数据的特定方式进行访问和操作的接口。

包括了多个具体的视图类型,比如 TypedArray 和 DataView。

js 复制代码
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;
}
  1. 第一种情况,检查全局是否存在 ArrayBuffer 对象,并且该对象具有 isView 方法来进行条件判断

    • 如果满足这个条件,它调用 ArrayBuffer.isView(val) 方法来判断 val 是否为 ArrayBuffer 视图。
  2. 第二种情况,它进一步判断 val 是否满足以下条件:

    • val 不为 nullundefined
    • val 具有 buffer 属性。
    • val.buffer 是一个 ArrayBuffer 对象。

3.6 isTypedArray

TypedArray(类型化数组)是 JavaScript 中的一组特定类型的数组,用于处理和操作二进制数据。它们提供了更高效和灵活的方式来读取、写入和操作 ArrayBuffer 中的数据。

TypedArray 包括以下几种类型:

  • Int8Array: 有符号8位整数数组
  • Uint8Array: 无符号8位整数数组
  • Uint8ClampedArray: 无符号8位整数数组(值被限制在0-255之间)
  • Int16Array: 有符号16位整数数组
  • Uint16Array: 无符号16位整数数组
  • Int32Array: 有符号32位整数数组
  • Uint32Array: 无符号32位整数数组
  • Float32Array: 32位浮点数数组
  • Float64Array: 64位浮点数数组
  • BigInt64Array: 64位整数数组
  • BigUint64Array: 无符号64位整数数组

这些 TypedArray 类型提供了一系列方法和属性,使得对二进制数据的访问和操作更加高效和方便。你可以使用 TypedArray 来创建、读取和修改 ArrayBuffer 中的数据。

js 复制代码
const isTypedArray = (TypedArray => {
  return thing => {
    return TypedArray && thing instanceof TypedArray;
  };
})(typeof Uint8Array !== 'undefined' && getPrototypeOf(Uint8Array));

采用了立即调用函数表达式(IIFE)的方式,传入了一个具体的 TypedArray 类型作为参数。

  1. 判断全局是否存在 TypedArray 对象,并且检查给定的 TypedArray 类型是否为 Uint8Array 或其子类。如果满足条件,就将 TypedArray 赋值为 Uint8Array 类型的原型对象(prototype),否则为 undefined
  2. 使用高阶函数,检查 TypedArray 是否存在,判断 thing 是否为该类型的实例来确定 thing 是否为 TypedArray。

3.7 trim

去除首尾空格:

js 复制代码
const trim = (str) => str.trim ?
  str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
  1. 检查传入的字符串 str 是否具有 trim 方法。如果有,就调用 str.trim() 来直接去除字符串两端的空格。
  2. 如果传入的字符串 str 没有 trim 方法,则执行正则表达式替换
  • 正则表达式 ^[\s\uFEFF\xA0]+ 匹配字符串开头的所有空格字符(空格、换行、制表符等)。
  • 正则表达式 [\s\uFEFF\xA0]+$ 匹配字符串末尾的所有空格字符。
  • 使用空字符串 '' 进行替换,从而去除字符串两端的空格。

3.8 forEach

重写forEach方法,可以遍历数组或对象:

js 复制代码
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') {
    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);
    }
  }
}
  1. 如果参数 objnullundefined,则函数会直接返回
  2. 如果 obj 是一个数组,遍历数组中的每个元素,回调函数 fn。回调函数接受三个参数:元素的值、元素的索引和原始数组对象
  3. 如果 obj 是一个对象,则会迭代遍历对象的键,并对每个键对应的值应用回调函数 fn
    1. 通过 { allOwnKeys: true }选项确定枚举范围
    2. true Object.getOwnPropertyNames包括不可枚举属性,包括原型链属性
    3. false Object.keys 包括可枚举属性,不包括原型链属性
    4. 遍历执行回调函数 fn.call(null, ...) ,三个参数是:值,键名,原始数组对象或对象。

3.9 findKey

找到对象中是否存在该属性:

js 复制代码
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;
}
  1. 将传入的 key 转换为小写字母形式,并保存在变量 key 中。
  2. 使用 Object.keys(obj) 获取给定对象的所有键,并将结果保存在变量 keys 中。
  3. 使用一个 while 循环来遍历 keys 数组中的键。
  4. 在循环中,将当前键保存在变量 _key 中。
  5. 比较 key_key 的小写形式是否相等。如果相等,则说明找到了对应的键,直接返回 _key
  6. 如果在循环结束后仍未找到匹配的键,则函数返回 null

3.10 _global

判断指定全局对象:

js 复制代码
const _global = (() => {
  if (typeof globalThis !== "undefined") return globalThis;
  return typeof self !== "undefined" ? self : (typeof window !== 'undefined' ? window : global)
})();
  1. 检查当前环境是否支持 globalThis 对象。globalThis 是 ES2020 新增的全局对象,用于在不同环境中统一访问全局作用域。如果当前环境支持返回 globalThis
  2. 如果当前环境不支持 globalThis,则检查是否存在 self 对象。self 对象在浏览器环境中表示全局对象 windowframes[0]。如果存在则返回 self
  3. 如果当前环境既不支持 globalThis,也不存在 self 对象,则检查是否存在 window 对象。window 对象是浏览器环境中的全局对象。如果存在则返回 window
  4. 如果以上条件均不满足,则默认使用 Node.js 环境中的 global 对象作为全局对象。

3.11 isContextDefined

判断上下文是否定义:

js 复制代码
const isContextDefined = (context) => !isUndefined(context) && context !== _global;
  1. 使用 isUndefined(context) 判断给定的上下文 context 是否是未定义
  2. 如果上下文 context 是未定义的则返回 false,表示上下文未定义
  3. 否则,继续检查上下文 context 是否等于全局对象 _global。表示上下文已定义但是等于全局对象,说明未定义上下文。

3.12 merge

用于合并多个对象。函数的参数可以传入任意数量的对象,它会将这些对象的属性合并到一个新的对象中,并返回合并后的结果。

js 复制代码
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;
}
  1. 在函数体内部,首先使用 isContextDefined(this) 来判断当前环境是否定义了上下文,并且该上下文不等于全局对象 _global。如果上下文已定义且满足条件,将其解构赋值给变量 {caseless};否则,将一个空对象 {} 赋给 {caseless}
  2. 创建一个空对象 result,用于保存合并结果。
  3. 使用 for 循环遍历传入的所有参数(即传入的对象),对每个对象执行合并操作。
  4. 在循环中,首先检查当前对象参数是否为真值(即非 nullundefined)。如果是假值,则跳过该参数,不进行处理。
  5. 如果当前对象参数为真值,则使用 forEach 函数对该对象的属性进行迭代。forEach 函数是可以遍历对象和数组的方法,传入对象和处理方法。
  6. assignValue接受值和键:
    • 首先判断键是否在已经存在
    • 如果值存在且是一个纯对象,则调用 merge 函数将两个对象合并
    • 如果不存在且是一个纯对象,直接使用merge 与空对象合并
    • 如果是数组,则直接浅拷贝赋值
    • 其他直接进行赋值
  7. 最后,函数返回合并后的结果对象 result

3.13 extend

extend 函数用于将对象 b 的属性扩展到对象 a 上。它遍历对象 b 的属性,并根据一些条件将属性值赋给对象 a 的相应属性。

js 复制代码
function bind(fn, thisArg) {
  return function wrap() {
    return fn.apply(thisArg, arguments);
  };
}


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
}

bind 函数:

  1. bind 函数接收两个参数:

    • fn:要绑定的函数。
    • thisArg:要将函数绑定到的对象。
  2. 返回一个新的函数 wrap,当调用该函数时,会以 thisArg 作为上下文(即 this 值),并使用 apply 方法将参数传递给原始函数 fn

  3. 使用 apply 方法调用原始函数 fn,并传入所需的上下文和参数。


  1. extend 函数接收四个参数:

    • a:目标对象,属性将被合并到该对象。
    • b:源对象,用于获取属性进行合并的对象。
    • thisArg:可选参数,用于绑定源对象属性值的上下文。
    • { allOwnKeys } = {}:可选参数,一个包含属性的对象,其中的 allOwnKeys 属性用于指示是否遍历所有自有属性。
  2. 在函数体内部,使用 forEach 函数对源对象 b 进行遍历操作。

  3. 对于每个属性,首先判断是否存在 thisArg 并且当前属性值是一个函数。若满足条件,则将该属性绑定到 thisArg 上,并将绑定后的函数赋值给目标对象 a 的相应键名上。

  4. 如果不满足上述条件,直接将源对象属性值赋值给目标对象 a 的相应键名上。

  5. 注意,在 forEach 函数的第三个参数中传入 { allOwnKeys },用于指示是否遍历所有自有属性。

3.14 stripBOM

去除 BOM 的内容:

js 复制代码
const stripBOM = content => {
    if (content.charCodeAt(0) === 0xfeff) {
        content = content.slice(1)
    }
    return content
}
  1. 使用 charCodeAt 方法获取传入内容的第一个字符的 Unicode 编码。
  2. 判断第一个字符的编码是否为 0xfeff,即判断是否为 BOM 的 Unicode 编码。
  3. 如果是 BOM 编码,则通过 slice 方法去除第一个字符。
  4. 最后,返回处理后的内容。

3.15 inherits

用于实现继承:

js 复制代码
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)
}
  1. 接收四个参数:

    • constructor:子类的构造函数。
    • superConstructor:父类的构造函数。
    • props:可选参数,一个包含子类附加属性/方法的对象。
    • descriptors:可选参数,一个描述符对象,用于定义属性的特性。
  2. 使用 Object.create 方法创建一个原型链,将父类的原型作为子类的原型对象,并可以通过 descriptors 对其进行配置。

  3. 将子类的原型对象的 constructor 属性设置为子类自身的构造函数,以免在继承时改变原始构造函数的引用。

  4. 使用 Object.defineProperty 方法定义一个不可枚举的 super 属性,其值为父类的原型对象,以便在子类中访问父类的原型。

  5. 如果传入了 props 参数,则使用 Object.assign 方法将这些属性/方法添加到子类的原型对象上。

  6. 注意,inherit 函数并没有返回任何值,只是对构造函数和原型进行了相应的处理以实现继承关系。

3.16 toFlatObject

用于将源对象扁平化合并到目标对象中:

js 复制代码
const toFlatObject = (sourceObj, destObj, filter, propFilter) => {
	let props
	let i
	let prop
	const merged = {}

	destObj = destObj || {}

	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
}
  1. 接收四个参数:

    • sourceObj:源对象,需要被扁平化合并的对象。
    • destObj:目标对象,合并后的结果将写入到该对象中。
    • filter:可选参数,一个过滤函数,用于指定哪些对象会被遍历和合并。如果为 false,则不执行过滤。
    • propFilter:可选参数,一个属性过滤函数,用于指定哪些属性会被合并。如果为空或返回 true,则合并对应属性。
  2. 初始化一些变量,包括 props(用于存储源对象的属性名数组)、i(用于遍历属性名数组的索引)、prop(用于存储当前遍历到的属性名)和 merged(用于记录已经合并过的属性)。

  3. 如果目标对象 destObj 为空,将其赋值为空对象 {}

  4. 判断源对象 sourceObj 是否为 nullundefined,若是,则直接返回目标对象 destObj

  5. 使用 do...while 循环的方式进行属性的扁平化合并。循环条件包括:

    • props 赋值为源对象的属性名数组。

    • 使用 while 循环遍历属性名数组,从后向前遍历。

    • 获取当前遍历到的属性名,并进行判断筛选:

      • 如果存在 propFilter 并且通过 propFilter 的判断,则判断是否已经合并过该属性,如果没有则将该属性的值合并到目标对象中,并将该属性记录到 merged 对象中以表示已经合并过。
    • 在每次循环结束后,获取源对象的原型,并赋值给 sourceObj,以便继续下一次循环。

  6. 最后,返回合并后的目标对象 destObj

3.17 endsWith

查找结尾字符:

js 复制代码
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
}
  1. 接收三个参数:

    • str:要检查的字符串。
    • searchString:要比较的字符串,用于检查是否是 str 的结尾部分。
    • position:可选参数,用于限制搜索的结束位置,默认值为 str 的长度。
  2. str 转换为字符串类型。

  3. 检查 position 是否未定义或大于 str 的长度。如果是,则将 position 设置为 str 的长度。

  4. 通过减去 searchString 的长度,计算在 str 上搜索的起始位置。

  5. 使用 indexOf 方法在 str 中从 position 开始搜索 searchString,并将结果保存到 lastIndex 变量中。

  6. 返回一个布尔值,判断 lastIndex 是否不等于 -1 并且等于 position。只有当 searchStringstr 中出现在指定位置时,才返回 true,否则返回 false

3.18 toArray

转换数组,如果输入已经是数组,则直接返回;如果输入是一个类似数组的对象(拥有length属性),则将其转换为真正的数组;否则返回null表示转换失败。

js 复制代码
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
}
  1. 如果thing为空或未定义,那么直接返回null
  2. 如果thing已经是一个数组,则直接返回它本身,无需转换。
  3. 获取thing的长度,并将其赋值给变量i。如果i不是一个有效的数字,那么返回null。这是为了确保thing具有有效的长度。
  4. 创建一个具有长度为i的新数组arr
  5. 使用循环,从thing的最后一个元素开始,逐个将其赋值给arr对应位置的元素。这样可以保持原始顺序。
  6. 返回转换后的数组arr

3.19 forEachEntry

实现普通 Object 对象的每一个键值对都传入给一个回调函数进行处理的功能:

js 复制代码
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])
	}
}
  1. 使用Symbol.iterator属性获取对象obj的迭代器生成器。
  2. 调用迭代器生成器,返回一个迭代器对象iterator。这样我们就可以通过调用iterator.next()来获取对象的每个条目。
  3. 定义一个变量result,用于存储每次迭代的结果。
  4. 使用循环来遍历迭代器,通过调用iterator.next()来获取下一个条目。当result.donefalse时,表示还有未遍历的条目,继续循环。
  5. 通过result.value获取当前条目的值,该值为一个包含键值对的数组。
  6. 调用回调函数fn,并使用call方法将obj作为上下文传入。同时将当前的键和值作为参数传递给回调函数。

3.20 matchAll

匹配所有正则的字符:

js 复制代码
const matchAll = (regExp, str) => {
	let matches
	const arr = []

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

	return arr
}
  1. 声明一个变量matches,用于存储每次正则表达式匹配的结果。
  2. 声明一个空数组arr,用于存储所有匹配的结果。
  3. 进入while循环,条件为matches = regExp.exec(str)不等于null,即每次执行正则表达式的exec方法匹配字符串str,并将匹配结果赋值给matches。
  4. 如果匹配结果不为null,即还有匹配的内容,将匹配结果push到数组arr中。
  5. 循环结束后,返回存储所有匹配结果的数组arr。

3.21 toCamelCase

通过将连字符、下划线和空格后的小写字母转换为大写字母,驼峰命名法格式

js 复制代码
const toCamelCase = str => {
    return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m, p1, p2) {
        return p1.toUpperCase() + p2
    })
}
  1. 首先,将传入的字符串转换为小写字母,使用str.toLowerCase()方法。
  2. 使用正则表达式/-_\s(\w*)/g来匹配字符串中的连字符、下划线和空格,以及它们后面的一个小写字母和零个或多个单词字符。
  3. 使用replace()方法来替换匹配到的字符串,替换函数中的参数m代表匹配到的字符串,p1代表第一个捕获组即小写字母,p2代表第二个捕获组即单词字符。
  4. 替换函数返回p1转换为大写字母后与p2拼接的结果,即将匹配到的字符串转换为驼峰命名法格式。
  5. 最后,函数返回转换后的字符串。

3.22 hasOwnProperty

检查对象(不包含继承)是否具有指定的属性:

js 复制代码
const hasOwnProperty = (({ hasOwnProperty }) =>
    (obj, prop) =>hasOwnProperty.call(obj, prop)
)(Object.prototype)

它是一个柯里化函数:

  1. 通过解构赋值从Object.prototype中获取hasOwnProperty方法,并将其赋值给函数的参数hasOwnProperty。
  2. 然后,定义了一个匿名函数,该函数接受两个参数obj和prop,用于检查对象obj是否具有属性prop。
  3. 在匿名函数中,通过调用hasOwnProperty.call(obj, prop)来检查对象obj是否具有属性prop。这里使用了call方法来确保在obj上调用正确的hasOwnProperty方法。
  4. 将匿名函数作为结果返回。

3.23 reduceDescriptors

应用reducer函数来减少对象的属性描述符,并将处理后的属性描述符重新定义到原始对象上:

js 复制代码
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)
}
js 复制代码
const object1 = {
  property1: 42,
};
// { value: 42, writable: true, enumerable: true, configurable: true }
const descriptors1 = Object.getOwnPropertyDescriptors(object1);

代码步骤解释:

  1. 首先,使用Object.getOwnPropertyDescriptors(obj)方法获取对象obj的所有属性描述符,并将其存储在descriptors变量中。
  2. 声明一个空对象reducedDescriptors,用于存储经过reducer函数处理后的属性描述符。
  3. 使用forEach函数遍历descriptors中的每个属性描述符和对应的名称。
  4. 在forEach的回调函数中,调用reducer函数,并传入属性描述符descriptor、属性名称name和原始对象obj作为参数。如果reducer函数的返回值不为false,则将该属性描述符添加到reducedDescriptors对象中。
  5. 循环结束后,使用Object.defineProperties(obj, reducedDescriptors)方法将reducedDescriptors中的属性描述符定义到原始对象obj上。

3.24 freezeMethods

用于冻结对象中的方法:

js 复制代码
const freezeMethods = obj => {
    reduceDescriptors(obj, (descriptor, name) => {
        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 + "'")
            }
        }
    })
}

代码步骤解释:

  1. 首先,调用reduceDescriptors函数来减少对象的属性描述符。
  2. 在reduceDescriptors的回调函数中,判断如果obj是一个函数,并且属性名称为['arguments', 'caller', 'callee']中的一项,那么跳过该属性的处理,返回false。
  3. 获取属性值value。
  4. 如果value不是一个函数,则直接返回。
  5. 将属性描述符descriptor的enumerable属性设置为false,使方法不可枚举。
  6. 如果descriptor中存在writable属性,则将其设置为false,并返回。
  7. 如果descriptor中不存在set方法,则将其设置为一个函数,该函数会抛出一个错误,提示无法重写只读方法。
  8. reduceDescriptors函数执行完毕后,对象中的方法被冻结。

3.25 toObjectSet

将数组或字符串转换为一个包含唯一值的对象:

js 复制代码
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
}
  1. 首先,声明一个空对象obj,用于存储结果。
  2. 声明一个名为define的函数,该函数接受一个数组作为参数,并将数组中的每个值作为属性添加到obj对象中,并将属性值设置为true。
  3. 使用三元运算符判断arrayOrString是否为数组。如果是数组,则调用define函数并将arrayOrString作为参数传入;如果不是数组,则将arrayOrString转换为字符串,并使用split(delimiter)方法将其拆分为数组,然后再调用define函数。
  4. 最后,返回包含唯一值的对象集合obj。

3.26 toFiniteNumber

传入的value转换为有限的数字:

js 复制代码
const toFiniteNumber = (value, defaultValue) => {
	value = +value
	return Number.isFinite(value) ? value : defaultValue
}

它接受两个参数:value和defaultValue

代码步骤解释:

  1. 首先,使用一元加号运算符将value转换为数字类型。
  2. 使用Number.isFinite()方法检查转换后的值是否为有限的数字。如果是有限的数字,则返回转换后的值;如果不是有限的数字,则返回defaultValue。

3.27 generateString

用于生成指定大小和字母表的随机字符串:

js 复制代码
const ALPHA = 'abcdefghijklmnopqrstuvwxyz'

const DIGIT = '0123456789'

const ALPHABET = {
	DIGIT,
	ALPHA,
	ALPHA_DIGIT: ALPHA + ALPHA.toUpperCase() + DIGIT
}

const generateString = (size = 16, alphabet = ALPHABET.ALPHA_DIGIT) => {
	let str = ''
	const { length } = alphabet
	while (size--) {
		str += alphabet[(Math.random() * length) | 0]
	}

	return str
}
  1. 首先,定义了三个常量:ALPHA表示小写字母表,DIGIT表示数字表,ALPHABET是一个包含不同字母表的对象,其中DIGIT和ALPHA分别表示数字表和小写字母表,而ALPHA_DIGIT表示由小写字母表、大写字母表和数字表组成的组合字母表。
  2. 接下来,定义了一个名为generateString的函数,它接受两个参数:size(默认为16)和alphabet(默认为ALPHABET.ALPHA_DIGIT)。
  3. 在函数内部,声明了一个空字符串str,用于存储生成的随机字符串。
  4. 使用解构赋值获取字母表alphabet的长度。
  5. 使用while循环,循环size次,每次循环时,从字母表alphabet中随机选择一个字符,并将其拼接到str字符串中,按位或去除小数
  6. 循环结束后,返回生成的随机字符串str。

3.28 isSpecCompliantForm

并检查该参数是否符合特定规范的表单对象:

js 复制代码
function isSpecCompliantForm(thing) {
	return !!(thing && isFunction(thing.append) && thing[Symbol.toStringTag] === 'FormData' && thing[Symbol.iterator])
}
  1. 首先,使用逻辑与运算符(&&)来确保thing存在且具有一个名为append的函数。
  2. 接着,使用[Symbol.toStringTag]属性来检查thing是否具有一个值为'FormData'的特殊标记。
  3. 最后,使用[Symbol.iterator]属性来检查thing是否具有一个迭代器对象。
  4. 如果上述所有条件都满足,则返回true,否则返回false。

3.29 toJSONObject

将给定的对象转换为JSON对象。它通过递归地访问对象的属性,并在转换过程中处理循环引用的情况:

js 复制代码
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)
}
  1. 首先,声明一个长度为10的数组stack,用于存储已访问的对象。
  2. 声明一个名为visit的函数,该函数接受两个参数:source和i。它用于递归地访问对象的属性。
  3. 在visit函数中,首先判断source是否为对象。如果不是对象,则直接返回source。
    1. 如果source是一个对象,那么首先检查stack数组中是否已经存在source,如果存在则说明对象已经被访问过,直接返回。
    2. 接下来,检查source对象是否具有'toJSON'属性。如果没有'toJSON'属性,则说明对象需要进行转换。
    3. 将source对象添加到stack数组中,然后根据source是数组还是对象,创建一个空的目标对象target。
    4. 使用forEach函数遍历source对象的每个属性,对每个属性值进行递归调用visit函数,并将结果赋值给reducedValue。
    5. 如果reducedValue不是undefined,则将其添加到target对象的对应属性中。
    6. 将stack数组中的当前对象置为undefined,以便其他对象可以访问它。
    7. 最后,visit函数返回转换后的target对象。
  4. toJSONObject函数最终调用visit函数,并将obj作为参数传入。

4. 总结

本文主要介绍了axios源码中utils.js中的非常实用的工具函数;通过本次源码学习可以了解工具函数中使用的技巧方法,为后期源码阅读打下基础。由于水平有限,文中如有错误,请指正^O^。

相关推荐
Catfood_Eason9 分钟前
HTML5 盒子模型
前端·html
小李小李不讲道理15 分钟前
「Ant Design 组件库探索」二:Tag组件
前端·react.js·ant design
1024小神19 分钟前
在rust中执行命令行输出中文乱码解决办法
前端·javascript
wordbaby20 分钟前
React Router v7 中的 `Layout` 组件工作原理
前端·react.js
旺仔牛仔QQ糖21 分钟前
Vue为普通函数添加防抖功能(基于Pinia 插件为action 配置防抖功能 引发思考)
前端·vue.js
lyc23333326 分钟前
鸿蒙Next人脸比对技术:轻量化模型的智能应用
前端
*小雪32 分钟前
vue2使用vue-cli脚手架搭建打包加密方法-JavaScript obfuscator
前端·javascript·vue.js
Coca1 小时前
Vue 3 缩放盒子组件 ScaleBox:实现内容动态缩放与坐标拾取偏移矫正
前端
枫叶kx1 小时前
发布一个angular的npm包(包含多个模块)
前端·npm·angular.js
工呈士1 小时前
Webpack 剖析与策略
前端·面试·webpack