所有工具函数
还是老样子,先看看axios
的工具函数有哪些,先心里有个印象,然后再逐个分析。
直接拉到最下面,可以看到axios
的工具函数都是统一导出的:
export default {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 对象
};
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
的工具函数多了很多,同时也发现有很多同名的工具函数,例如:
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
来实现的,上面这些相同的会在源码阅读中移除。
正式阅读
这里的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
对象的静态方法;
参考:
- Buffer.isBuffer()
- Object.prototype.constructor
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
的实例;
参考:
- 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
方法;
参考:
- 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
的判断;
参考:
- Object.getPrototypeOf()
- 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-namesreturn 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 providedif (obj === null || typeof obj === 'undefined') {return;}let i;let l;// Force an array if not already something iterableif (typeof obj !== 'object') {/*eslint no-param-reassign:0*/obj = [obj];}if (isArray(obj)) {// Iterate over array valuesfor (i = 0, l = obj.length; i < l; i++) {fn.call(null, obj[i], i, obj);}} else {// Iterate over object keysconst 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
,然后遍历;
参考:
- Object.getOwnPropertyNames()
- Object.keys()
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,eqeqeqif (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 || 0let i = list.length - startconst 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
循环来遍历对象的键值对;
参考:
- Symbol.iterator
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
循环来匹配所有的字符串,然后将匹配到的字符串放入数组中;
参考:
- RegExp.prototype.exec()
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
函数来遍历对象的所有属性描述符;
参考:
- Object.getOwnPropertyDescriptors()
- Object.defineProperties()
freezeMethods
/**
* Makes all methods read-only
* @param {Object} obj
*/
const freezeMethods = (obj) => {reduceDescriptors(obj, (descriptor, name) => {// skip restricted props in strict modeif (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
;
参考:
- self
- global
- this
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
的应用更加明显和全面,就是看着有点扣脑阔,但是这个对于个人的提升还是很有帮助的。
最后
整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。
有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享
部分文档展示:
文章篇幅有限,后面的内容就不一一展示了
有需要的小伙伴,可以点下方卡片免费领取