继上次阅读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
的应用更加明显和全面,就是看着有点扣脑阔,但是这个对于个人的提升还是很有帮助的。