【源码阅读】axios的46个工具函数
继上次阅读Vue2
的工具函数后,这次来阅读axios
的工具函数,来看看两个库的工具函数有什么不同或者相同的地方。
这次直接使用github1s
来阅读源码,因为是工具函数,调试可以直接在浏览器控制台,这样方便很多,地址:github1s.com/axios/axios…
所有工具函数
还是老样子,先看看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
:判断是否是 ArrayBufferisBuffer
:判断是否是 BufferisFormData
:判断是否是 FormDataisArrayBufferView
:判断是否是 ArrayBufferViewisString
:判断是否是字符串isNumber
:判断是否是数字isBoolean
:判断是否是布尔值isObject
:判断是否是对象isPlainObject
:判断是否是纯对象isUndefined
:判断是否是 undefinedisDate
:判断是否是日期isFile
:判断是否是文件isBlob
:判断是否是 BlobisRegExp
:判断是否是正则isFunction
:判断是否是函数isStream
:判断是否是 StreamisURLSearchParams
:判断是否是 URLSearchParamsisTypedArray
:判断是否是 TypedArrayisFileList
:判断是否是 FileListforEach
:遍历对象merge
:合并对象extend
:扩展对象trim
:去除字符串两边的空格stripBOM
:去除字符串的 BOMinherits
:继承toFlatObject
:将对象转换为扁平对象kindOf
:获取对象的类型kindOfTest
:判断对象的类型endsWith
,// 判断字符串是否以指定的字符串结尾toArray
:将类数组转换为数组forEachEntry
:遍历对象的键值对matchAll
:匹配所有的字符串isHTMLForm
:判断是否是 HTMLFormhasOwnProperty
:判断对象是否有指定的属性hasOwnProp
: hasOwnProperty, :判断对象是否有指定的属性reduceDescriptors
:降低描述符freezeMethods
:冻结方法toObjectSet
:将数组或者字符串转换为类似Set
的对象toCamelCase
:将字符串转换为驼峰命名noop
:空函数toFiniteNumber
:将字符串转换为有限数字findKey
:查找对象的键global: _global
:全局对象isContextDefined
:判断上下文是否定义toJSONObject
:将字符串转换为 JSON 对象
一共有46
个工具函数,比Vue2
的工具函数多了很多,同时也发现有很多同名的工具函数,例如:
isArray
isRegExp
isFunction
isObject
isPlainObject
extend
noop
别看这么多,其中对于类型判断的函数只有少数几个需要关注,其他的都是使用同样的方式来实现的,如下:
isArrayBuffer
isBuffer
isString
isNumber
isUndefined
isDate
isFile
isBlob
isRegExp
isFunction
isStream
isURLSearchParams
isFileList
isHTMLForm
一共14
个,这些函数都是通过kindOf
来实现的,上面这些相同的会在源码阅读中移除。
正式阅读
这里的kindOf
和kindOfTest
优先阅读,因为这两个函数是用来判断类型的,上面移除的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
函数的实现依赖了isUndefined
和isFunction
函数,而isUndefined
和isFunction
函数的实现依赖了typeOfTest
函数;
这个在上面的kindOfTest
函数中已经介绍过了,所以这里就一口气了解三个函数,当然主角还是isBuffer
函数;
isBuffer
函数的实现是判断val
是否是Buffer
,这里的判断需要满足以下条件:
val
不是null
val
不是undefined
val
的constructor
不是null
val
的constructor
不是undefined
val
的constructor
的isBuffer
方法是一个函数val
的constructor
的isBuffer
方法返回true
总体来说还是很复杂的,这里的主角是val
的constructor
的isBuffer
方法,这个方法是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
是一个函数thing
是FormData
的实例thing
的类型是[object FormData]
thing
的toString
方法是一个函数thing
的toString
方法返回[object FormData]
thing
的toString
方法返回[object FormData]
边界限定条件比较多,可以是多种情况,最主要的还是thing
是FormData
的实例;
参考:
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.toStringTag
、Symbol.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
函数的实现依赖了isObject
和isFunction
函数;
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
函数的实现是一个立即执行函数,返回一个函数,也是一个高阶函数;
这里的判断是为了兼容IE
,IE
中没有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
函数更加简洁;
Vue2
的toArray
函数:
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)/g
,axios
中的正则是/[_-\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
运算符来判断self
、global
、this
是否存在,如果存在,就将它们赋值给_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
的工具函数写的比较好,这里只是为了对比vue2
和axios
的工具函数,看看他们的不同之处。
对于vue2
的工具函数来说,它的侧重点是针对vue2
的,所以会比较关注vue
系统运行过程中的处理,对于数据类型种类这一块就没axios
这么全面;
对于axios
的工具函数来说,它的侧重点是针对网络请求,而且还是跨平台的,所以会比较关注数据的处理,对于数据类型种类这一块就比vue2
这么全面;
总结
axios
的工具函数使用了很多高阶函数来处理,整体对于闭包、高阶函数、函数柯里化等都有一定的应用;
axios
的工具函数对于数据类型种类非常全面,囊括浏览器到node
的大多数数据类型;
axios
的工具函数对于函数签名非常全面,对于函数的参数类型、返回值类型都有非常详细的描述,写的非常规范,这个值得我们学习;
主要还是高阶函数的应用,这个在vue2
中也有一定的应用,但是axios
的应用更加明显和全面,就是看着有点扣脑阔,但是这个对于个人的提升还是很有帮助的。
转载自:https://juejin.cn/post/7174682752938147901