Lodash源码阅读-includes

157 阅读5分钟

功能概述

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;
}

实现思路

  1. 首先将集合规范化:如果是类数组(包括数组和字符串),直接使用;如果是普通对象,则转换为值数组
  2. 处理 fromIndex 参数:转换为整数,并处理负值情况,确保有效的起始索引
  3. 对字符串类型使用原生 indexOf 方法进行高效查找
  4. 对数组类型使用 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 方法相比,有以下几个主要区别:

  1. 通用性更强

    • Lodash 的 includes 可以处理数组、字符串和普通对象
    • 原生方法分散在不同类型上:Array.prototype.includes()String.prototype.includes(),不能直接用于对象
  2. 对象处理能力

    • Lodash 会自动将普通对象转换为值数组(通过 values 函数)
    • 原生方法没有针对对象的 includes 实现
  3. 特殊值处理

    • Lodash 的 includes 通过 baseIndexOf 正确处理 NaN 值的查找
    • 原生 Array.includes() 也能处理 NaN,但实现方式不同
  4. 集合类型自动识别

    • Lodash 根据输入类型自动选择最佳查找策略
    • 原生方法需要明确知道操作的是数组还是字符串
  5. 非数组类型的集合支持

    • Lodash 可以处理类数组对象(如 arguments、NodeList 等)
    • 原生方法仅适用于真正的数组或字符串
  6. 实现差异

    • Lodash 内部使用 isArrayLikevaluesbaseIndexOf 等辅助函数
    • 原生方法直接基于 JavaScript 引擎实现,通常性能更好

总结

  1. includes 函数通过类型检测和条件处理,实现了对多种集合类型的统一查找接口
  2. 函数内部的参数处理体现了防御性编程思想,确保各种边界情况下的正确行为
  3. 针对不同数据类型采用不同查找策略,平衡了性能和功能需求
  4. 合理使用现有工具函数,体现了模块化设计的思想,便于代码维护和理解