Lodash 源码阅读-arrayLikeKeys
功能概述
arrayLikeKeys
是 Lodash 库中的一个内部工具函数,用于获取类数组对象(array-like object)的可枚举属性名称。它能够处理多种类型的类数组对象,包括数组、arguments 对象、Buffer、TypedArray 等,并且可以选择是否包含继承的属性。这个函数是 Lodash 中 keys
和 keysIn
方法的核心实现之一,为对象属性遍历提供了基础支持。
前置学习
依赖函数
arrayLikeKeys
依赖以下几个 Lodash 函数:
- isArray:检查值是否为数组
- isArguments:检查值是否为 arguments 对象
- isBuffer:检查值是否为 Buffer 对象
- isTypedArray:检查值是否为 TypedArray 对象
- isIndex:检查值是否为有效的数组索引
- baseTimes:基础的 times 函数实现,用于创建指定长度的数组
技术知识
- 类数组对象:具有 length 属性和索引元素的对象,如数组、arguments、NodeList 等
- 可枚举属性:可以通过 for...in 循环遍历的属性
- 继承属性:通过原型链继承的属性,而非对象自身的属性
- JavaScript 中的特殊对象:如 arguments、Buffer、TypedArray 等
源码实现
javascript
/**
* Creates an array of the enumerable property names of the array-like `value`.
*
* @private
* @param {*} value The value to query.
* @param {boolean} inherited Specify returning inherited property names.
* @returns {Array} Returns the array of property names.
*/
function arrayLikeKeys(value, inherited) {
var isArr = isArray(value),
isArg = !isArr && isArguments(value),
isBuff = !isArr && !isArg && isBuffer(value),
isType = !isArr && !isArg && !isBuff && isTypedArray(value),
skipIndexes = isArr || isArg || isBuff || isType,
result = skipIndexes ? baseTimes(value.length, String) : [],
length = result.length;
for (var key in value) {
if (
(inherited || hasOwnProperty.call(value, key)) &&
!(
skipIndexes &&
// Safari 9 has enumerable `arguments.length` in strict mode.
(key == "length" ||
// Node.js 0.10 has enumerable non-index properties on buffers.
(isBuff && (key == "offset" || key == "parent")) ||
// PhantomJS 2 has enumerable non-index properties on typed arrays.
(isType &&
(key == "buffer" || key == "byteLength" || key == "byteOffset")) ||
// Skip index properties.
isIndex(key, length))
)
) {
result.push(key);
}
}
return result;
}
实现思路
arrayLikeKeys
函数的实现思路可以分为以下几个步骤:
- 类型判断:首先判断传入的值是什么类型的类数组对象(数组、arguments、Buffer 或 TypedArray)
- 预处理索引:对于具有数值索引的类数组对象,预先创建包含所有索引的结果数组
- 属性遍历:使用 for...in 循环遍历对象的所有可枚举属性
- 属性过滤:根据不同类型的类数组对象,过滤掉特定的属性和索引属性
- 结果构建:将符合条件的属性名添加到结果数组中
这种实现方式既高效又灵活,能够处理各种类型的类数组对象,并且可以选择是否包含继承的属性。
源码解析
参数解析
javascript
function arrayLikeKeys(value, inherited) {
函数接收两个参数:
value
:要查询的类数组对象inherited
:是否包含继承的属性,如果为 true,则包含继承的属性;如果为 false,则只包含对象自身的属性
类型判断
javascript
var isArr = isArray(value),
isArg = !isArr && isArguments(value),
isBuff = !isArr && !isArg && isBuffer(value),
isType = !isArr && !isArg && !isBuff && isTypedArray(value),
skipIndexes = isArr || isArg || isBuff || isType,
这段代码判断 value
的具体类型:
isArr
:检查是否为数组isArg
:检查是否为 arguments 对象(且不是数组)isBuff
:检查是否为 Buffer 对象(且不是数组或 arguments)isType
:检查是否为 TypedArray 对象(且不是前三种类型)skipIndexes
:如果是上述四种类型之一,则为 true,表示需要跳过索引属性
这种级联式的判断确保了每个类型变量的互斥性,一个对象只能属于其中一种类型。
预处理索引
javascript
(result = skipIndexes ? baseTimes(value.length, String) : []),
(length = result.length);
如果 value
是具有数值索引的类数组对象(skipIndexes
为 true),则使用 baseTimes
函数预先创建一个包含所有索引的数组:
baseTimes(value.length, String)
创建一个长度为value.length
的数组,每个元素是对应索引的字符串形式- 例如,如果
value.length
为 3,则result
初始为['0', '1', '2']
这样做的好处是可以一次性处理所有的索引属性,避免在后续的 for...in 循环中重复处理。
属性遍历和过滤
javascript
for (var key in value) {
if (
(inherited || hasOwnProperty.call(value, key)) &&
!(
skipIndexes &&
// Safari 9 has enumerable `arguments.length` in strict mode.
(key == "length" ||
// Node.js 0.10 has enumerable non-index properties on buffers.
(isBuff && (key == "offset" || key == "parent")) ||
// PhantomJS 2 has enumerable non-index properties on typed arrays.
(isType &&
(key == "buffer" || key == "byteLength" || key == "byteOffset")) ||
// Skip index properties.
isIndex(key, length))
)
) {
result.push(key);
}
}
这段代码是函数的核心部分,它遍历对象的所有可枚举属性,并根据条件过滤:
-
继承属性检查 :
(inherited || hasOwnProperty.call(value, key))
- 如果
inherited
为 true,则包含继承的属性 - 如果
inherited
为 false,则只包含对象自身的属性(使用hasOwnProperty
检查)
- 如果
-
特殊属性和索引过滤 :
!(skipIndexes && (...))
- 如果
skipIndexes
为 true(表示是类数组对象),则需要过滤以下属性:'length'
属性(所有类数组对象都有)- Buffer 对象的
'offset'
和'parent'
属性 - TypedArray 对象的
'buffer'
、'byteLength'
和'byteOffset'
属性 - 所有索引属性(使用
isIndex
函数检查)
- 如果
-
属性添加 :
result.push(key)
- 如果属性通过了上述所有检查,则将其添加到结果数组中
这种复杂的条件判断确保了只有符合要求的属性才会被添加到结果中,避免了重复和特殊属性的干扰。
返回结果
javascript
return result;
最后,函数返回包含所有符合条件的属性名的数组。
与其他函数的关系
arrayLikeKeys
是 Lodash 中处理对象属性的一系列函数中的一环,它与以下函数密切相关:
-
keys:获取对象自身的可枚举属性名
javascriptfunction keys(object) { return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); }
-
keysIn:获取对象自身和继承的可枚举属性名
javascriptfunction keysIn(object) { return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); }
-
baseKeys:获取非类数组对象的自身可枚举属性名
javascriptfunction baseKeys(object) { if (!isPrototype(object)) { return nativeKeys(object); } var result = []; for (var key in Object(object)) { if (hasOwnProperty.call(object, key) && key != "constructor") { result.push(key); } } return result; }
-
isArrayLike:检查值是否为类数组对象
javascriptfunction isArrayLike(value) { return value != null && isLength(value.length) && !isFunction(value); }
这些函数共同构成了 Lodash 中处理对象属性的完整体系,能够处理各种类型的对象和属性。
总结
arrayLikeKeys
是 Lodash 中一个重要的内部工具函数,用于获取类数组对象的可枚举属性名。它通过一系列的类型检查和条件过滤,确保能够正确处理各种类型的类数组对象,并且可以选择是否包含继承的属性。
这个函数的主要特点包括:
- 类型适应性:能够处理多种类型的类数组对象,包括数组、arguments、Buffer、TypedArray 等
- 性能优化:通过预处理索引属性,避免了在遍历过程中的重复处理
- 兼容性:考虑了不同环境和浏览器的特殊情况,如 Safari 的 arguments.length、Node.js 的 Buffer 属性等
- 灵活性:可以选择是否包含继承的属性,适应不同的使用场景
- 可靠性:通过严格的条件过滤,确保结果的准确性和一致性
理解 arrayLikeKeys
的工作原理,有助于我们更好地使用 Lodash 的 keys
和 keysIn
方法,并在自己的代码中实现更高效、更可靠的对象属性处理逻辑。