功能概述
includes 函数是 Lodash 中用于检查集合是否包含指定值的工具函数。它不仅能检查数组,还能检查对象和字符串,具有很强的通用性。函数支持自定义起始位置,可以从集合的任意位置开始查找,同时对 NaN 等特殊值有良好的处理。
前置学习
依赖函数
- isArrayLike:判断值是否为类数组对象,用于确定如何处理输入集合
- values:获取对象的所有属性值数组,用于将对象转换为可迭代的值数组
- toInteger:将值转换为整数,用于处理 fromIndex 参数
- nativeMax:原生 Math.max 函数,用于确保 fromIndex 不小于 0
- isString:判断值是否为字符串,用于确定使用哪种查找方法
- baseIndexOf:在数组中查找值的基础实现函数,支持 NaN 值查找
技术知识
- 类数组对象:理解什么是类数组对象及如何处理
- JavaScript 中的查找算法:数组的 indexOf 方法和基础索引查找算法
- 类型判断:如何判断不同的数据类型
- NaN 处理:理解 NaN 不等于自身的特性及其处理方法
源码实现
function includes(collection, value, fromIndex, guard) {
collection = isArrayLike(collection) ? collection : values(collection);
fromIndex = fromIndex && !guard ? toInteger(fromIndex) : 0;
var length = collection.length;
if (fromIndex < 0) {
fromIndex = nativeMax(length + fromIndex, 0);
}
return isString(collection)
? fromIndex <= length && collection.indexOf(value, fromIndex) > -1
: !!length && baseIndexOf(collection, value, fromIndex) > -1;
}
实现思路
- 首先将集合规范化:如果是类数组(包括数组和字符串),直接使用;如果是普通对象,则转换为值数组
- 处理 fromIndex 参数:转换为整数,并处理负值情况,确保有效的起始索引
- 对字符串类型使用原生 indexOf 方法进行高效查找
- 对数组类型使用 baseIndexOf 进行查找,支持特殊值(如 NaN)的比较
源码解析
1. 集合类型处理与规范化
collection = isArrayLike(collection) ? collection : values(collection);
这行代码判断输入的集合是否为类数组。如果是(包括数组、字符串、arguments 对象等),则直接使用;如果不是(如普通对象),则用 values() 方法提取所有属性值组成数组。
例如:
// 数组处理
includes([1, 2, 3], 2);
// collection 保持为 [1, 2, 3]
// 对象处理
includes({ a: 1, b: 2 }, 2);
// collection 被转换为 [1, 2]
2. 起始索引参数处理
fromIndex = fromIndex && !guard ? toInteger(fromIndex) : 0;
这行代码处理起始索引参数。如果提供了有效的 fromIndex 且没有 guard 参数(内部使用标记),则将其转换为整数;否则默认为 0。
var length = collection.length;
if (fromIndex < 0) {
fromIndex = nativeMax(length + fromIndex, 0);
}
然后获取集合长度,并处理负索引情况。负索引表示从末尾开始计数,例如 -1 表示最后一个元素。代码确保处理后的索引不会小于 0。
例如:
// 处理负索引
includes([1, 2, 3], 3, -2);
// fromIndex 为 -2,length 为 3
// fromIndex 被调整为 max(3 + (-2), 0) = max(1, 0) = 1
// 从索引 1 开始查找,即从第二个元素开始
3. 字符串和数组的不同处理逻辑
return isString(collection)
? fromIndex <= length && collection.indexOf(value, fromIndex) > -1
: !!length && baseIndexOf(collection, value, fromIndex) > -1;
这部分根据集合类型采用不同的查找策略:
- 对于字符串:使用原生的
String.prototype.indexOf方法,该方法性能更高 - 对于数组和其他类数组对象:使用
baseIndexOf函数,它能正确处理 NaN 等特殊值
两种情况都会进行边界检查:
- 对于字符串,确保 fromIndex 不超过字符串长度
- 对于数组,确保数组非空(通过
!!length)
并且都通过检查索引是否大于 -1 来判断是否找到了目标值。
例如:
// 字符串查找
includes("abcd", "bc", 0);
// 使用 'abcd'.indexOf('bc', 0) > -1,结果为 true
// 数组查找(包含 NaN)
includes([1, NaN, 3], NaN);
// 使用 baseIndexOf 函数,能正确找到 NaN,结果为 true
与原生方法的比较
Lodash 的 includes 函数与 JavaScript 原生的 includes 方法相比,有以下几个主要区别:
-
通用性更强:
- Lodash 的
includes可以处理数组、字符串和普通对象 - 原生方法分散在不同类型上:
Array.prototype.includes()和String.prototype.includes(),不能直接用于对象
- Lodash 的
-
对象处理能力:
- Lodash 会自动将普通对象转换为值数组(通过
values函数) - 原生方法没有针对对象的 includes 实现
- Lodash 会自动将普通对象转换为值数组(通过
-
特殊值处理:
- Lodash 的
includes通过baseIndexOf正确处理 NaN 值的查找 - 原生
Array.includes()也能处理 NaN,但实现方式不同
- Lodash 的
-
集合类型自动识别:
- Lodash 根据输入类型自动选择最佳查找策略
- 原生方法需要明确知道操作的是数组还是字符串
-
非数组类型的集合支持:
- Lodash 可以处理类数组对象(如 arguments、NodeList 等)
- 原生方法仅适用于真正的数组或字符串
-
实现差异:
- Lodash 内部使用
isArrayLike、values、baseIndexOf等辅助函数 - 原生方法直接基于 JavaScript 引擎实现,通常性能更好
- Lodash 内部使用
总结
includes函数通过类型检测和条件处理,实现了对多种集合类型的统一查找接口- 函数内部的参数处理体现了防御性编程思想,确保各种边界情况下的正确行为
- 针对不同数据类型采用不同查找策略,平衡了性能和功能需求
- 合理使用现有工具函数,体现了模块化设计的思想,便于代码维护和理解