1. 前言
-
本文参加了由 公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
-
这是源码共读的第19期,链接:axios 工具函数
3. 源码工具函数
3.1 类型判断
类型判断主要通过Object.prototype.toString.call
和typeof
方法进行判断。
通用的类型判断工具函数:
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);
}
- 使用
kindOf
函数判断val
的类型是否为object
- 然后获取
val
的原型对象,并分别与null
和Object.prototype
进行比较。如果原型对象等于null
,或者等于Object.prototype
,或者其上一级原型对象为null
,则继续判断。 - 判断给定对象
val
是否具有Symbol.toStringTag
属性,并且不具有Symbol.iterator
属性。(Symbol.toStringTag in val)
判断给定对象val
是否具有Symbol.toStringTag
属性。Symbol.toStringTag
属性是用来定制对象在调用Object.prototype.toString()
方法时返回的字符串标签。如果val
具有Symbol.toStringTag
属性,则表示它可能是一个特定类型的对象,而不仅仅是一个纯对象。!(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);
}
- 判断
val
不为null
和undefined
val.constructor !== null
:检查val
的构造函数不为null
,以避免使用了null
构造出的对象!isUndefined(val.constructor)
:确保构造函数存在- 使用
typeof
操作符检查val.constructor.isBuffer
的类型是否为函数 - 调用构造函数自身的
isBuffer
方法进行判断
3.4 isFormData
判断是否是formData,FormData
接口提供了一种表示表单数据的键值对 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]') )
)
)
}
- 判断是否为
null
或undefined
- 判断是否存在 FormData 构造函数,并且
thing
是该构造函数的实例 - 如果第一种判断不成立,它将进一步检查
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;
}
-
第一种情况,检查全局是否存在 ArrayBuffer 对象,并且该对象具有
isView
方法来进行条件判断- 如果满足这个条件,它调用
ArrayBuffer.isView(val)
方法来判断val
是否为 ArrayBuffer 视图。
- 如果满足这个条件,它调用
-
第二种情况,它进一步判断
val
是否满足以下条件:val
不为null
或undefined
。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 类型作为参数。
- 判断全局是否存在 TypedArray 对象,并且检查给定的 TypedArray 类型是否为
Uint8Array
或其子类。如果满足条件,就将TypedArray
赋值为Uint8Array
类型的原型对象(prototype),否则为undefined
。 - 使用高阶函数,检查
TypedArray
是否存在,判断thing
是否为该类型的实例来确定thing
是否为 TypedArray。
3.7 trim
去除首尾空格:
js
const trim = (str) => str.trim ?
str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
- 检查传入的字符串
str
是否具有trim
方法。如果有,就调用str.trim()
来直接去除字符串两端的空格。 - 如果传入的字符串
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);
}
}
}
- 如果参数
obj
为null
或undefined
,则函数会直接返回 - 如果
obj
是一个数组,遍历数组中的每个元素,回调函数fn
。回调函数接受三个参数:元素的值、元素的索引和原始数组对象 - 如果
obj
是一个对象,则会迭代遍历对象的键,并对每个键对应的值应用回调函数fn
。- 通过
{ allOwnKeys: true }
选项确定枚举范围 - true Object.getOwnPropertyNames包括不可枚举属性,包括原型链属性
- false Object.keys 包括可枚举属性,不包括原型链属性
- 遍历执行回调函数
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;
}
- 将传入的
key
转换为小写字母形式,并保存在变量key
中。 - 使用
Object.keys(obj)
获取给定对象的所有键,并将结果保存在变量keys
中。 - 使用一个
while
循环来遍历keys
数组中的键。 - 在循环中,将当前键保存在变量
_key
中。 - 比较
key
和_key
的小写形式是否相等。如果相等,则说明找到了对应的键,直接返回_key
。 - 如果在循环结束后仍未找到匹配的键,则函数返回
null
。
3.10 _global
判断指定全局对象:
js
const _global = (() => {
if (typeof globalThis !== "undefined") return globalThis;
return typeof self !== "undefined" ? self : (typeof window !== 'undefined' ? window : global)
})();
- 检查当前环境是否支持
globalThis
对象。globalThis
是 ES2020 新增的全局对象,用于在不同环境中统一访问全局作用域。如果当前环境支持返回globalThis
。 - 如果当前环境不支持
globalThis
,则检查是否存在self
对象。self
对象在浏览器环境中表示全局对象window
或frames[0]
。如果存在则返回self
。 - 如果当前环境既不支持
globalThis
,也不存在self
对象,则检查是否存在window
对象。window
对象是浏览器环境中的全局对象。如果存在则返回window
。 - 如果以上条件均不满足,则默认使用 Node.js 环境中的
global
对象作为全局对象。
3.11 isContextDefined
判断上下文是否定义:
js
const isContextDefined = (context) => !isUndefined(context) && context !== _global;
- 使用
isUndefined(context)
判断给定的上下文context
是否是未定义 - 如果上下文
context
是未定义的则返回false
,表示上下文未定义 - 否则,继续检查上下文
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;
}
- 在函数体内部,首先使用
isContextDefined(this)
来判断当前环境是否定义了上下文,并且该上下文不等于全局对象_global
。如果上下文已定义且满足条件,将其解构赋值给变量{caseless}
;否则,将一个空对象{}
赋给{caseless}
。 - 创建一个空对象
result
,用于保存合并结果。 - 使用
for
循环遍历传入的所有参数(即传入的对象),对每个对象执行合并操作。 - 在循环中,首先检查当前对象参数是否为真值(即非
null
和undefined
)。如果是假值,则跳过该参数,不进行处理。 - 如果当前对象参数为真值,则使用
forEach
函数对该对象的属性进行迭代。forEach
函数是可以遍历对象和数组的方法,传入对象和处理方法。 - assignValue接受值和键:
- 首先判断键是否在已经存在
- 如果值存在且是一个纯对象,则调用
merge
函数将两个对象合并 - 如果不存在且是一个纯对象,直接使用
merge
与空对象合并 - 如果是数组,则直接浅拷贝赋值
- 其他直接进行赋值
- 最后,函数返回合并后的结果对象
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
函数:
-
bind
函数接收两个参数:fn
:要绑定的函数。thisArg
:要将函数绑定到的对象。
-
返回一个新的函数
wrap
,当调用该函数时,会以thisArg
作为上下文(即this
值),并使用apply
方法将参数传递给原始函数fn
。 -
使用
apply
方法调用原始函数fn
,并传入所需的上下文和参数。
-
extend
函数接收四个参数:a
:目标对象,属性将被合并到该对象。b
:源对象,用于获取属性进行合并的对象。thisArg
:可选参数,用于绑定源对象属性值的上下文。{ allOwnKeys } = {}
:可选参数,一个包含属性的对象,其中的allOwnKeys
属性用于指示是否遍历所有自有属性。
-
在函数体内部,使用
forEach
函数对源对象b
进行遍历操作。 -
对于每个属性,首先判断是否存在
thisArg
并且当前属性值是一个函数。若满足条件,则将该属性绑定到thisArg
上,并将绑定后的函数赋值给目标对象a
的相应键名上。 -
如果不满足上述条件,直接将源对象属性值赋值给目标对象
a
的相应键名上。 -
注意,在
forEach
函数的第三个参数中传入{ allOwnKeys }
,用于指示是否遍历所有自有属性。
3.14 stripBOM
去除 BOM 的内容:
js
const stripBOM = content => {
if (content.charCodeAt(0) === 0xfeff) {
content = content.slice(1)
}
return content
}
- 使用
charCodeAt
方法获取传入内容的第一个字符的 Unicode 编码。 - 判断第一个字符的编码是否为
0xfeff
,即判断是否为 BOM 的 Unicode 编码。 - 如果是 BOM 编码,则通过
slice
方法去除第一个字符。 - 最后,返回处理后的内容。
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)
}
-
接收四个参数:
constructor
:子类的构造函数。superConstructor
:父类的构造函数。props
:可选参数,一个包含子类附加属性/方法的对象。descriptors
:可选参数,一个描述符对象,用于定义属性的特性。
-
使用
Object.create
方法创建一个原型链,将父类的原型作为子类的原型对象,并可以通过descriptors
对其进行配置。 -
将子类的原型对象的
constructor
属性设置为子类自身的构造函数,以免在继承时改变原始构造函数的引用。 -
使用
Object.defineProperty
方法定义一个不可枚举的super
属性,其值为父类的原型对象,以便在子类中访问父类的原型。 -
如果传入了
props
参数,则使用Object.assign
方法将这些属性/方法添加到子类的原型对象上。 -
注意,
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
}
-
接收四个参数:
sourceObj
:源对象,需要被扁平化合并的对象。destObj
:目标对象,合并后的结果将写入到该对象中。filter
:可选参数,一个过滤函数,用于指定哪些对象会被遍历和合并。如果为false
,则不执行过滤。propFilter
:可选参数,一个属性过滤函数,用于指定哪些属性会被合并。如果为空或返回true
,则合并对应属性。
-
初始化一些变量,包括
props
(用于存储源对象的属性名数组)、i
(用于遍历属性名数组的索引)、prop
(用于存储当前遍历到的属性名)和merged
(用于记录已经合并过的属性)。 -
如果目标对象
destObj
为空,将其赋值为空对象{}
。 -
判断源对象
sourceObj
是否为null
或undefined
,若是,则直接返回目标对象destObj
。 -
使用
do...while
循环的方式进行属性的扁平化合并。循环条件包括:-
props
赋值为源对象的属性名数组。 -
使用
while
循环遍历属性名数组,从后向前遍历。 -
获取当前遍历到的属性名,并进行判断筛选:
- 如果存在
propFilter
并且通过propFilter
的判断,则判断是否已经合并过该属性,如果没有则将该属性的值合并到目标对象中,并将该属性记录到merged
对象中以表示已经合并过。
- 如果存在
-
在每次循环结束后,获取源对象的原型,并赋值给
sourceObj
,以便继续下一次循环。
-
-
最后,返回合并后的目标对象
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
}
-
接收三个参数:
str
:要检查的字符串。searchString
:要比较的字符串,用于检查是否是str
的结尾部分。position
:可选参数,用于限制搜索的结束位置,默认值为str
的长度。
-
将
str
转换为字符串类型。 -
检查
position
是否未定义或大于str
的长度。如果是,则将position
设置为str
的长度。 -
通过减去
searchString
的长度,计算在str
上搜索的起始位置。 -
使用
indexOf
方法在str
中从position
开始搜索searchString
,并将结果保存到lastIndex
变量中。 -
返回一个布尔值,判断
lastIndex
是否不等于 -1 并且等于position
。只有当searchString
在str
中出现在指定位置时,才返回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
}
- 如果
thing
为空或未定义,那么直接返回null
。 - 如果
thing
已经是一个数组,则直接返回它本身,无需转换。 - 获取
thing
的长度,并将其赋值给变量i
。如果i
不是一个有效的数字,那么返回null
。这是为了确保thing
具有有效的长度。 - 创建一个具有长度为
i
的新数组arr
。 - 使用循环,从
thing
的最后一个元素开始,逐个将其赋值给arr
对应位置的元素。这样可以保持原始顺序。 - 返回转换后的数组
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])
}
}
- 使用
Symbol.iterator
属性获取对象obj
的迭代器生成器。 - 调用迭代器生成器,返回一个迭代器对象
iterator
。这样我们就可以通过调用iterator.next()
来获取对象的每个条目。 - 定义一个变量
result
,用于存储每次迭代的结果。 - 使用循环来遍历迭代器,通过调用
iterator.next()
来获取下一个条目。当result.done
为false
时,表示还有未遍历的条目,继续循环。 - 通过
result.value
获取当前条目的值,该值为一个包含键值对的数组。 - 调用回调函数
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
}
- 声明一个变量matches,用于存储每次正则表达式匹配的结果。
- 声明一个空数组arr,用于存储所有匹配的结果。
- 进入while循环,条件为matches = regExp.exec(str)不等于null,即每次执行正则表达式的exec方法匹配字符串str,并将匹配结果赋值给matches。
- 如果匹配结果不为null,即还有匹配的内容,将匹配结果push到数组arr中。
- 循环结束后,返回存储所有匹配结果的数组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
})
}
- 首先,将传入的字符串转换为小写字母,使用str.toLowerCase()方法。
- 使用正则表达式/-_\s(\w*)/g来匹配字符串中的连字符、下划线和空格,以及它们后面的一个小写字母和零个或多个单词字符。
- 使用replace()方法来替换匹配到的字符串,替换函数中的参数m代表匹配到的字符串,p1代表第一个捕获组即小写字母,p2代表第二个捕获组即单词字符。
- 替换函数返回p1转换为大写字母后与p2拼接的结果,即将匹配到的字符串转换为驼峰命名法格式。
- 最后,函数返回转换后的字符串。
3.22 hasOwnProperty
检查对象(不包含继承)是否具有指定的属性:
js
const hasOwnProperty = (({ hasOwnProperty }) =>
(obj, prop) =>hasOwnProperty.call(obj, prop)
)(Object.prototype)
它是一个柯里化函数:
- 通过解构赋值从Object.prototype中获取hasOwnProperty方法,并将其赋值给函数的参数hasOwnProperty。
- 然后,定义了一个匿名函数,该函数接受两个参数obj和prop,用于检查对象obj是否具有属性prop。
- 在匿名函数中,通过调用hasOwnProperty.call(obj, prop)来检查对象obj是否具有属性prop。这里使用了call方法来确保在obj上调用正确的hasOwnProperty方法。
- 将匿名函数作为结果返回。
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);
代码步骤解释:
- 首先,使用Object.getOwnPropertyDescriptors(obj)方法获取对象obj的所有属性描述符,并将其存储在descriptors变量中。
- 声明一个空对象reducedDescriptors,用于存储经过reducer函数处理后的属性描述符。
- 使用forEach函数遍历descriptors中的每个属性描述符和对应的名称。
- 在forEach的回调函数中,调用reducer函数,并传入属性描述符descriptor、属性名称name和原始对象obj作为参数。如果reducer函数的返回值不为false,则将该属性描述符添加到reducedDescriptors对象中。
- 循环结束后,使用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 + "'")
}
}
})
}
代码步骤解释:
- 首先,调用reduceDescriptors函数来减少对象的属性描述符。
- 在reduceDescriptors的回调函数中,判断如果obj是一个函数,并且属性名称为['arguments', 'caller', 'callee']中的一项,那么跳过该属性的处理,返回false。
- 获取属性值value。
- 如果value不是一个函数,则直接返回。
- 将属性描述符descriptor的enumerable属性设置为false,使方法不可枚举。
- 如果descriptor中存在writable属性,则将其设置为false,并返回。
- 如果descriptor中不存在set方法,则将其设置为一个函数,该函数会抛出一个错误,提示无法重写只读方法。
- 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
}
- 首先,声明一个空对象obj,用于存储结果。
- 声明一个名为define的函数,该函数接受一个数组作为参数,并将数组中的每个值作为属性添加到obj对象中,并将属性值设置为true。
- 使用三元运算符判断arrayOrString是否为数组。如果是数组,则调用define函数并将arrayOrString作为参数传入;如果不是数组,则将arrayOrString转换为字符串,并使用split(delimiter)方法将其拆分为数组,然后再调用define函数。
- 最后,返回包含唯一值的对象集合obj。
3.26 toFiniteNumber
传入的value转换为有限的数字:
js
const toFiniteNumber = (value, defaultValue) => {
value = +value
return Number.isFinite(value) ? value : defaultValue
}
它接受两个参数:value和defaultValue
代码步骤解释:
- 首先,使用一元加号运算符将value转换为数字类型。
- 使用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
}
- 首先,定义了三个常量:ALPHA表示小写字母表,DIGIT表示数字表,ALPHABET是一个包含不同字母表的对象,其中DIGIT和ALPHA分别表示数字表和小写字母表,而ALPHA_DIGIT表示由小写字母表、大写字母表和数字表组成的组合字母表。
- 接下来,定义了一个名为generateString的函数,它接受两个参数:size(默认为16)和alphabet(默认为ALPHABET.ALPHA_DIGIT)。
- 在函数内部,声明了一个空字符串str,用于存储生成的随机字符串。
- 使用解构赋值获取字母表alphabet的长度。
- 使用while循环,循环size次,每次循环时,从字母表alphabet中随机选择一个字符,并将其拼接到str字符串中,
按位或去除小数
。 - 循环结束后,返回生成的随机字符串str。
3.28 isSpecCompliantForm
并检查该参数是否符合特定规范的表单对象:
js
function isSpecCompliantForm(thing) {
return !!(thing && isFunction(thing.append) && thing[Symbol.toStringTag] === 'FormData' && thing[Symbol.iterator])
}
- 首先,使用逻辑与运算符(&&)来确保thing存在且具有一个名为append的函数。
- 接着,使用[Symbol.toStringTag]属性来检查thing是否具有一个值为'FormData'的特殊标记。
- 最后,使用[Symbol.iterator]属性来检查thing是否具有一个迭代器对象。
- 如果上述所有条件都满足,则返回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)
}
- 首先,声明一个长度为10的数组stack,用于存储已访问的对象。
- 声明一个名为visit的函数,该函数接受两个参数:source和i。它用于递归地访问对象的属性。
- 在visit函数中,首先判断source是否为对象。如果不是对象,则直接返回source。
- 如果source是一个对象,那么首先检查stack数组中是否已经存在source,如果存在则说明对象已经被访问过,直接返回。
- 接下来,检查source对象是否具有'toJSON'属性。如果没有'toJSON'属性,则说明对象需要进行转换。
- 将source对象添加到stack数组中,然后根据source是数组还是对象,创建一个空的目标对象target。
- 使用forEach函数遍历source对象的每个属性,对每个属性值进行递归调用visit函数,并将结果赋值给reducedValue。
- 如果reducedValue不是undefined,则将其添加到target对象的对应属性中。
- 将stack数组中的当前对象置为undefined,以便其他对象可以访问它。
- 最后,visit函数返回转换后的target对象。
- toJSONObject函数最终调用visit函数,并将obj作为参数传入。
4. 总结
本文主要介绍了axios
源码中utils.js
中的非常实用的工具函数;通过本次源码学习可以了解工具函数中使用的技巧方法,为后期源码阅读打下基础。由于水平有限,文中如有错误,请指正^O^。