Lodash源码阅读-unionWith

215 阅读4分钟

Lodash 源码阅读-unionWith

概述

unionWith 函数是 Lodash 中用于合并数组的高级工具,它接受多个数组参数和一个比较器函数,用于自定义元素间的相等性判断。该函数返回一个新数组,包含所有数组的唯一值,其中重复性由比较器函数决定。结果数组中元素的顺序取决于它们在原数组中首次出现的位置。

前置学习

依赖函数

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

技术知识

  • 函数式编程中的高阶函数和参数柯里化
  • 自定义比较逻辑实现
  • 数组扁平化和去重算法
  • 不定参数处理机制
  • JavaScript 对象比较的局限性与解决方案

源码实现

var unionWith = baseRest(function (arrays) {
  var comparator = last(arrays);
  comparator = typeof comparator == "function" ? comparator : undefined;
  return baseUniq(
    baseFlatten(arrays, 1, isArrayLikeObject, true),
    undefined,
    comparator
  );
});

实现思路

unionWith 函数通过巧妙组合多个基础函数实现其功能。

  • 首先使用 baseRest 处理不定数量的数组参数;
  • 然后检查最后一个参数是否为函数类型,若是则将其作为比较器,否则设为 undefined;
  • 接着使用 baseFlatten 将所有输入数组扁平化为一维数组;
  • 最后调用 baseUniq 根据提供的比较器函数对扁平化数组进行去重,返回最终结果。 这种实现支持自定义相等性判断逻辑,使函数在处理复杂对象数组时更为灵活。

源码解析

1. 函数定义与参数处理

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

baseRest 在这里创建了一个新函数,将所有传入 unionWith 的参数收集到单个 arrays 数组中。这种设计允许函数接受任意数量的数组参数,实现了类似 ES6 中 ...args 的功能。

示例:当调用 unionWith(arr1, arr2, arr3, comparator) 时,arrays 将包含 [arr1, arr2, arr3, comparator]

2. 提取并验证比较器函数

var comparator = last(arrays);
comparator = typeof comparator == "function" ? comparator : undefined;

这段代码获取并验证比较器函数:

  1. 使用 last 函数获取 arrays 的最后一个元素
  2. 判断该元素是否为函数类型
    • 若是函数,则将其用作比较器
    • 若不是函数,则将比较器设为 undefined

这种处理确保了只有当最后一个参数确实是函数时,才会将其作为比较器使用,提高了函数的健壮性。

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

return baseUniq(
  baseFlatten(arrays, 1, isArrayLikeObject, true),
  undefined,
  comparator
);

这段代码完成了主要工作:

  1. baseFlatten(arrays, 1, isArrayLikeObject, true) - 将数组扁平化:

    • 深度参数为 1,只处理一层嵌套
    • 使用 isArrayLikeObject 确保只扁平化类数组对象
    • 最后一个参数 true 表示移除最后一个元素(比较器函数)
  2. baseUniq 对扁平化后的数组进行去重:

    • 第一个参数是扁平化后的数组
    • 第二个参数 undefined 表示不使用迭代器
    • 第三个参数是提取出的比较器函数

实际调用示例:

// 使用 _.isEqual 作为比较器
var objects = [
  { x: 1, y: 2 },
  { x: 2, y: 1 },
];
var others = [
  { x: 1, y: 1 },
  { x: 1, y: 2 },
];

_.unionWith(objects, others, _.isEqual);
// => [{ x: 1, y: 2 }, { x: 2, y: 1 }, { x: 1, y: 1 }]

在这个例子中,{ x: 1, y: 2 } 出现在两个数组中,通过 _.isEqual 比较被识别为相同对象,因此在结果中只出现一次。

比较器函数接收两个参数:当前处理的元素和结果数组中的某个元素。当比较器返回 true 时,表示两个元素相等,当前元素将被跳过。

总结

  1. 通过自定义比较函数实现复杂数据结构的相等性判断,解决了 JavaScript 原生全等比较的局限
  2. 使用函数式组合方式构建复杂功能,提高代码复用性和可维护性
  3. 优雅处理不定参数,通过参数位置和类型检测实现 API 的灵活性
  4. 结合扁平化和去重操作,在保持语义清晰的同时实现高效的数组处理