Lodash源码阅读-unionBy

251 阅读4分钟

Lodash 源码阅读-unionBy

概述

unionBy 函数是 Lodash 中用于合并多个数组并去除重复项的工具函数,它接受一个迭代器参数,用于在比较元素唯一性之前对数组中的每个元素进行转换。函数保持元素在结果数组中的顺序与它们在输入数组中首次出现的顺序一致,且总是优先保留第一个数组中的值。这个函数特别适合处理需要基于特定条件合并多个数组的场景。

前置学习

依赖函数

  • baseRest:模拟 ES6 的剩余参数功能,处理不定数量的参数
  • baseFlatten:将嵌套数组扁平化到指定深度
  • baseUniq:数组去重的基础实现函数
  • isArrayLikeObject:检查值是否为类数组对象
  • last:获取数组的最后一个元素
  • getIteratee:获取合适的迭代器函数

技术知识

  • 函数式编程中的高阶函数概念
  • 迭代器模式的应用
  • 数组扁平化和去重算法
  • 不定参数的处理技术
  • JavaScript 中的相等性比较机制

源码实现

var unionBy = baseRest(function (arrays) {
  var iteratee = last(arrays);
  if (isArrayLikeObject(iteratee)) {
    iteratee = undefined;
  }
  return baseUniq(
    baseFlatten(arrays, 1, isArrayLikeObject, true),
    getIteratee(iteratee, 2)
  );
});

实现思路

unionBy 函数的核心思路是处理任意数量的数组参数和一个可选的迭代器函数。首先使用 baseRest 处理不定参数,检查最后一个参数是否为迭代器函数。然后将所有输入数组扁平化为一维数组(深度为 1),仅处理类数组对象。接着使用 baseUniq 配合从 getIteratee 获取的迭代器函数对扁平化后的数组进行去重。最后返回处理结果,得到一个基于迭代器转换后的值去除重复项的合并数组。整个过程体现了函数式编程的组合理念。

源码解析

1. 函数定义与参数收集

var unionBy = baseRest(function (arrays) {
  // 函数体...
});

baseRest 函数在这里起到了关键作用,它实现了类似 ES6 的 rest 参数功能,将 unionBy 接收的所有参数收集到一个名为 arrays 的数组中。这使得 unionBy 函数能够接受任意数量的数组参数,非常灵活。

示例:

_.unionBy([2.1], [1.2, 2.3], Math.floor);
// arrays = [[2.1], [1.2, 2.3], Math.floor]

2. 迭代器参数处理

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

这段代码处理迭代器参数。last(arrays) 获取传入参数的最后一个元素,可能是迭代器函数或最后一个数组。使用 isArrayLikeObject 检查它是否为类数组对象:

  • 如果是类数组对象,说明用户没有提供迭代器函数,所有参数都是需要合并的数组,将 iteratee 设置为 undefined
  • 如果不是类数组对象,则保持不变,认为它是用户提供的迭代器函数

示例:

// iteratee = Math.floor (不是类数组对象)
_.unionBy([2.1], [1.2, 2.3], Math.floor);

// iteratee = undefined (最后一个参数是类数组对象)
_.unionBy([2.1], [1.2, 2.3]);

3. 数组扁平化与去重处理

return baseUniq(
  baseFlatten(arrays, 1, isArrayLikeObject, true),
  getIteratee(iteratee, 2)
);

这是函数的核心逻辑,完成两个主要步骤:

  1. 数组扁平化:使用 baseFlatten 将所有输入数组合并成一个一维数组

    • 参数 1 表示扁平化深度为 1
    • isArrayLikeObject 作为判断函数,确保只处理类数组对象
    • true 表示在扁平化过程中移除最后一个元素(如果它不是类数组对象)
  2. 去重处理:使用 baseUniq 对扁平化后的数组进行去重

    • getIteratee(iteratee, 2) 获取适当的迭代器函数
    • 参数 2 表示迭代器函数接受两个参数(值和索引)

getIteratee 函数非常灵活,能够处理多种形式的迭代器:

  • 函数:直接使用
  • 字符串:创建一个属性访问函数
  • 对象:创建一个属性匹配函数
  • undefined:使用默认的 identity 函数(返回原值)

示例:

// 使用函数迭代器
_.unionBy([2.1], [1.2, 2.3], Math.floor);
// => [2.1, 1.2]

// 使用属性名作为迭代器
_.unionBy([{ x: 1 }], [{ x: 2 }, { x: 1 }], "x");
// => [{ 'x': 1 }, { 'x': 2 }]

总结

通过分析 unionBy 函数的源码实现,我们可以学习到以下几个实用的编程技巧:

  1. 灵活的参数处理:使用 baseRest 处理不定参数,让函数接口更加灵活
  2. 多态迭代器设计:通过 getIteratee 支持多种形式的迭代器输入,提升 API 易用性
  3. 函数组合:将复杂功能分解为多个基础函数(如 baseFlattenbaseUniq)组合使用,提高代码复用性和可维护性
  4. 智能类型检测:利用 isArrayLikeObject 等工具函数进行类型判断,使函数在不同输入情况下都能稳健工作