本函数涉及到基本类型校验,数组扁平化,函数结果缓存以及正则表达式匹配对象属性(嵌套属性)相关的内容。at函数用于获取指定对象属性的值。
类型校验函数
getTag
利用Object.prototype.toString方法获取类型的tag([object Object]),并对null和undefined类型做特殊处理
const toString = Object.prototype.toString
function getTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]'
}
return toString.call(value)
}
isObjectLike
判断是否是一个对象类型的数据。借助typeof方法,并对null作单独处理。因为typeof null === 'object
function isObjectLike(value) {
return typeof value === 'object' && value !== null
}
isArguments
判断一个数据是否为arguments类型。借助了上述的getTag方法以及isObjectLike方
function isArguments(value) {
// isObjectLike 是否是一个对象 typeof value === 'object' && value !== null
return isObjectLike(value) && getTag(value) == '[object Arguments]'
}
isFlattenable
是否能被扁平化。@@isConcatSpreadable协议,用于判断一个对象是否能被concat函数扁平化。
const spreadableSymbol = Symbol.isConcatSpreadable
function isFlattenable(value) {
// 数组对象, arguments对象,或者是可扁平化对象
return Array.isArray(value) || isArguments(value) ||
!!(value && value[spreadableSymbol])
}
isKey
判断传入的值是否是一个对象的键
// 是否是一个.开头或[]包裹的属性
const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/
// 字母数字下划线属性,以0-9A-Za-z_开头并结尾的属性
const reIsPlainProp = /^\w*$/
function isKey(value, object) {
// 数组返回false
if (Array.isArray(value)) {
return false
}
const type = typeof value
// number boolean null symbol 返回 true
if (type === 'number' || type === 'boolean' || value == null || isSymbol(value)) {
return true
}
// 正则校验,普通属性,嵌套属性
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
(object != null && value in Object(object))
}
对reIsDeepProp正则校验进行代码拆分:
// 表达式1
const reIsDeepProp1 = /\[(?:[^[\]]*)\]/
// 去掉性能优化 (?:) 不捕获当前子表达式 匹配任意数量非 [ 或 ]
const reIsDeepProp2 = /\[[^[\]]*\]/
// 表达式2
const reIsDeepProp4 = /\[(?:(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/
// 去掉性能优化 (?:) 不捕获当前子表达式 非贪婪匹配"或'且未跟"或'且非\并以"或'结尾
const reIsDeepProp5 = /\[(["'])(?!\1)[^\\]*?\1\]/
// 表达式3
const reIsDeepProp7 = /\[(["'])(?:\\.)*?\1)\]/
// 去掉性能优化 (?:) 不捕获当前子表达式 非贪婪匹配"或'\并匹配任意除换行回车以外的字符
const reIsDeepProp8 = /\[(["'])\\.*?\1)\]/
不想看上述代码的可以直接看图:
reIsPlainProp正则:
isSymbol
借助getTag函数,兼容javascript早期版本
function isSymbol(value) {
const type = typeof value
return type == 'symbol' || (type === 'object' && value != null && getTag(value) == '[object Symbol]')
}
数组扁平化
依赖于上面的isFlattenable函数,作为是否可扁平化的校验
/**
* The base implementation of `flatten` with support for restricting flattening.
*
* @private
* @param {Array} array 需要扁平化的数组
* @param {number} depth 最大递归深度
* @param {boolean} [predicate=isFlattenable] The function invoked per iteration.每次迭代调用的函数
* @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. 限制数组值通过predicate类型校验
* @param {Array} [result=[]] The initial result value.
* @returns {Array} Returns the new flattened array.
*/
function baseFlatten(array, depth, predicate, isStrict, result) {
// predicate 是否可扁平化校验,可扁平化的类型,默认数组,Arguments,是否实现了Symbol.isConcatSpreadable协议
predicate || (predicate = isFlattenable)
// 返回值,默认空数组
result || (result = [])
// array 为 null 或者 undefined时直接返回空数组
if (array == null) {
return result
}
for (const value of array) {
// 递归深度大于0 并且能够被扁平化
if (depth > 0 && predicate(value)) {
// 大于 1 层,递归遍历
if (depth > 1) {
// Recursively flatten arrays (susceptible to call stack limits).
baseFlatten(value, depth - 1, predicate, isStrict, result)
} else {
// 等于 1 层 直接 解构
result.push(...value)
}
// 是否强制通过predicate 扁平化校验
} else if (!isStrict) {
result[result.length] = value
}
}
return result
}
函数结果缓存
memoize
func需要执行的函数,resolver用于生成缓存的唯一key值,未穿默认使用函数的第一个实参作为cache key。
函数结果缓存在一个Map对象上
function memoize(func, resolver) {
if (typeof func !== 'function' || (resolver != null && typeof resolver !== 'function')) {
throw new TypeError('Expected a function')
}
const memoized = function(...args) {
// 利用提供给 memoized 的参数生成 caceh key 或者使用 第一个实参作为 caceh key
const key = resolver ? resolver.apply(this, args) : args[0]
// 获取cache map
const cache = memoized.cache
// 如果有缓存,则返回对应的缓存
if (cache.has(key)) {
return cache.get(key)
}
// 否则,执行func
const result = func.apply(this, args)
// 并缓存在 当前 函数引用的cache 属性上
memoized.cache = cache.set(key, result) || cache
return result
}
// memoized 的 cache 默认使用 memoize.Cache 的 Map 对象
memoized.cache = new (memoize.Cache || Map)
return memoized
}
memoize.Cache = Map
export default memoize
memoizeCapped
在memoize函数的基础上,限制了最大缓存数量。达到最大缓存数量的限制时,清空缓存Map对象
const MAX_MEMOIZE_SIZE = 500
function memoizeCapped(func) {
const result = memoize(func, (key) => {
// 获取memoize函数上的cache Map对象
const { cache } = result
// 达到最大缓存数量限制, 清空缓存
if (cache.size === MAX_MEMOIZE_SIZE) {
cache.clear()
}
// 返回 cache key,默认为传入参数的第一个参数的值
return key
})
return result
}
工具函数
stringToPath
将一个嵌套对象路径转化为数组,如[a].b.c 转化为[a, b, c]。主要借助replace方法实现
该函数借助了memoizeCapped函数对执行结果进行缓存,提高二次执行性能
// 46
const charCodeOfDot = '.'.charCodeAt(0)
// 匹配转译字符\ 或 \\
const reEscapeChar = /\\(\\)?/g
const rePropName = RegExp(
// Match anything that isn't a dot or bracket.
// 非. 或 非 括号
'[^.[\\]]+' + '|' +
// Or match property names within brackets.
// \\[(?:([^"'][^[]*)|("')(?:(?!\2[^\\\\]|\\\\.)*?)\\2)]
'\\[(?:' +
// Match a non-string expression.
// 任意非"or非'非[
'([^"\'][^[]*)' + '|' +
// Or match strings (supports escaping characters).
'(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
')\\]'+ '|' +
// Or match "" as the space between consecutive dots or empty brackets.
'(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))'
, 'g')
const stringToPath = memoizeCapped((string) => {
const result = []
// 以.开头
if (string.charCodeAt(0) === charCodeOfDot) {
result.push('')
}
string.replace(rePropName, (match, expression, quote, subString) => {
// expression ([^"'][^[])
// quote ["']
// subString ((?:(?!\2)[^\\]|\\.)*?)
let key = match
if (quote) {
key = subString.replace(reEscapeChar, '$1')
}
else if (expression) {
key = expression.trim()
}
result.push(key)
})
return result
})
rePropName 转化为正则:
// 表达式1 非. [ ]
const rePropName1 = /[^.[\]]/
// 表达式2 非" 非' 非[
// 或 非贪婪匹配" 或 ' 未跟 " 或 '非\
// 或 非贪婪匹配\任意除换行会车以外的字符
// 匹配"或'
const rePropName2 = /\[(?:([^"'][^[])|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]/
// 表达式3 正向预查 . 或者 [] . 或者 [] 或者 结束符
const rePropName3 = /(?=(?:\.|\[\])(?:\.|\[\]|$))/
对应图如下:
castPath
把传入的对象键转化为数组。调用了isKey和stringToPath方法
/**
* Casts `value` to a path array if it's not one.
* 如果value不是路径数组则将它转换为一个路径数组
*
* @private
* @param {*} value The value to inspect.
* @param {Object} [object] The object to query keys on.
* @returns {Array} Returns the cast property path array.
*/
function castPath(value, object) {
// 数组直接返回
if (Array.isArray(value)) {
return value
}
// 否则如果是嵌套属性返回嵌套数组,不是嵌套属性则直接返回[对应的值]
return isKey(value, object) ? [value] : stringToPath(value)
}
toKey
依赖于isSymbol函数
把传入的值转换为一个key值,主要处理-0的情况。${-0} === '0'
/** Used as references for various `Number` constants. */
const INFINITY = 1 / 0
/**
* Converts `value` to a string key if it's not a string or symbol.
* 把value转换成一个键
*
* @private
* @param {*} value The value to inspect.
* @returns {string|symbol} Returns the key.
*/
function toKey(value) {
// string 或 symbol类型,直接返回
if (typeof value === 'string' || isSymbol(value)) {
return value
}
// 否则, 处理 -0 情况 后返回
const result = `${value}`
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result
}
baseGet
获取对象上属性的值
/**
* The base implementation of `get` without support for default values.
* get方法的基本实现,不返回对象默认值
*
* @private
* @param {Object} object The object to query.
* @param {Array|string} path The path of the property to get.
* @returns {*} Returns the resolved value.
*/
function baseGet(object, path) {
// 获取路径数组,如[prop],嵌套属性[a, b ,c]
path = castPath(path, object)
let index = 0
const length = path.length
while (object != null && index < length) {
// tokey处理-0情况,length > 1 的时候循环处理嵌套属性
// 对于.开头的path参数,castPath返回的数组的第一项为''
object = object[toKey(path[index++])]
}
return (index && index == length) ? object : undefined
}
get
相比较于baseGet函数,仅仅增加了一个形参defaultValue
function get(object, path, defaultValue) {
const result = object == null ? undefined : baseGet(object, path)
return result === undefined ? defaultValue : result
}
at函数
baseAt
依赖于get函数
/**
* The base implementation of `at` without support for individual paths.
* at 方法的基本实现,返回对应paths的值的数组
*
* @private
* @param {Object} object The object to iterate over.
* @param {string[]} paths The property paths to pick.
* @returns {Array} Returns the picked elements.
*/
function baseAt(object, paths) {
let index = -1
const length = paths.length
const result = new Array(length)
// object 为 null 或者 undefined时
const skip = object == null
// 下标0开始依次返回对应的值
while (++index < length) {
result[index] = skip ? undefined : get(object, paths[index])
}
return result
}
at
依赖于baseAt函数和baseFlatten函数
/**
* Creates an array of values corresponding to `paths` of `object`.
*
* @since 1.0.0
* @category Object
* @param {Object} object The object to iterate over.
* @param {...(string|string[])} [paths] The property paths to pick.
* @returns {Array} Returns the picked values.
* @example
*
* const object = { 'a': [{ 'b': { 'c': 3 } }, 4] }
*
* at(object, ['a[0].b.c', 'a[1]'])
* // => [3, 4]
*/
// baseFlatten 返回一个字符串数组,该函数可以处理字符串,类数组,一维数组和多维数组
const at = (object, ...paths) => baseAt(object, baseFlatten(paths, 1))