Lodash源码阅读-xorBy

163 阅读5分钟

Lodash 源码阅读-xorBy

概述

xorBy 是 Lodash 中的一个数组操作函数,它类似于 xor,但允许通过迭代器函数自定义比较标准。这个函数计算多个数组的异或结果,通过对每个元素应用迭代器函数生成比较值,然后基于这些值进行异或操作。它返回仅在一个数组中出现的元素集合,增强了数据比较的灵活性和精度。

前置学习

依赖函数

  • baseXor:内部实现异或操作的核心函数,执行多个数组间的对称差集计算
  • baseRest:处理函数的剩余参数,实现类似 ES6 的 rest 参数功能
  • last:获取数组的最后一个元素,用于提取迭代器函数参数
  • arrayFilter:过滤数组元素,确保只有有效的数组参与计算
  • isArrayLikeObject:检查值是否类似数组对象,用于参数验证
  • getIteratee:获取迭代器函数,支持多种迭代器形式(函数、字符串、对象等)

技术知识

  • 高阶函数:函数作为参数传递与处理的编程模式
  • 迭代器模式:使用迭代器函数处理集合元素的技术
  • 参数处理:处理可变参数和尾部特殊参数的方法
  • 集合操作:集合的异或(对称差)计算原理
  • 函数式编程:纯函数和函数组合等函数式编程概念

源码实现

var xorBy = baseRest(function (arrays) {
  var iteratee = last(arrays);
  if (isArrayLikeObject(iteratee)) {
    iteratee = undefined;
  }
  return baseXor(
    arrayFilter(arrays, isArrayLikeObject),
    getIteratee(iteratee, 2)
  );
});

实现思路

xorBy 函数的核心思路如下:

  1. 接收多个数组参数,并可能在最后一个参数位置提供迭代器函数
  2. 判断最后一个参数是否为迭代器函数(不是类数组对象则视为迭代器)
  3. 过滤掉非类数组对象的参数,确保只有有效数组参与计算
  4. 使用 baseXor 计算异或结果,同时应用迭代器函数处理每个元素

这种设计既支持灵活的参数传递方式,又通过迭代器函数支持自定义比较逻辑,使得函数能够处理复杂的数据比较场景。

源码解析

1. 函数定义与参数收集

var xorBy = baseRest(function(arrays) {

首先使用 baseRest 创建函数,它可以接收任意数量的参数并将它们收集到 arrays 数组中。这是一种模拟 ES6 rest 参数的实现方式,使得函数能够处理可变数量的输入数组。

例如,当用户调用 _.xorBy([1, 2], [2, 3], [3, 4], Math.floor) 时,所有参数都会被收集到 arrays 中,形成 [[1, 2], [2, 3], [3, 4], Math.floor]

2. 迭代器函数提取与验证

var iteratee = last(arrays);
if (isArrayLikeObject(iteratee)) {
  iteratee = undefined;
}

这段代码获取 arrays 的最后一个元素,并检查它是否为类数组对象。如果是类数组对象,说明用户没有提供迭代器函数,将 iteratee 设置为 undefined;如果不是类数组对象,则认为它是迭代器函数。

这种设计允许两种调用形式:

// 使用自定义迭代器函数
_.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);

// 使用属性路径作为迭代器
_.xorBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], "x");

在第一个例子中,Math.floor 被识别为迭代器函数;在第二个例子中,字符串 'x' 被识别为迭代器短语,后续会被转换为属性访问函数。

3. 参数过滤与异或计算

return baseXor(
  arrayFilter(arrays, isArrayLikeObject),
  getIteratee(iteratee, 2)
);

最后一步完成了两个关键操作:

  1. 使用 arrayFilter 过滤 arrays,只保留类数组对象,确保异或计算的输入有效
  2. 使用 getIteratee 处理迭代器函数,并将其与过滤后的数组一起传递给 baseXor 进行计算

getIteratee(iteratee, 2) 这个调用非常重要,它处理多种形式的迭代器:

  • 如果 iteratee 是函数,直接返回该函数
  • 如果 iteratee 是字符串,返回一个属性访问函数(如 _.property('x')
  • 如果 iteratee 是对象,返回一个匹配函数(如 _.matches({ 'x': 1 })
  • 如果 iterateeundefined,返回 Lodash 的 identity 函数

参数 2 表示返回的迭代器函数应接收两个参数,虽然在实际使用中迭代器通常只使用第一个参数(当前元素)。

4. 实际执行流程示例

当调用 _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor) 时,执行流程如下:

  1. arrays 被初始化为 [[2.1, 1.2], [2.3, 3.4], Math.floor]
  2. iteratee 被设置为 Math.floor(最后一个元素)
  3. 由于 Math.floor 不是类数组对象,所以 iteratee 保持不变
  4. arrayFilter 过滤 arrays,得到 [[2.1, 1.2], [2.3, 3.4]]
  5. getIteratee(Math.floor, 2) 返回 Math.floor 函数
  6. baseXor 对过滤后的数组计算异或,使用 Math.floor 作为比较标准
  7. 最终结果为 [1.2, 3.4],因为 Math.floor(2.1) === Math.floor(2.3),它们被视为相同元素

总结

xorBy 函数通过引入迭代器机制,显著增强了 xor 的功能,使其能够满足更复杂的数据比较需求。其设计优势包括:

  1. 灵活的比较方式:通过迭代器函数支持自定义元素比较标准,适用于复杂数据结构
  2. 简洁统一的接口:保持与其他 Lodash 函数一致的调用方式,降低学习成本
  3. 健壮的参数处理:严格的参数验证和过滤,确保函数行为稳定可预测
  4. 强大的迭代器支持:支持多种形式的迭代器表达,增强了函数的表达能力

这个函数特别适合处理需要按特定标准比较的数据集,例如忽略对象中特定属性进行比较、对数值进行舍入比较等场景,是 Lodash 中优雅设计和函数式编程思想的典型代表。