Lodash 源码阅读-uniqWith
概述
uniqWith
函数用于创建一个去重后的数组,通过提供一个自定义比较器函数来判断元素是否相同。它与基本的 uniq
函数不同,后者使用 SameValueZero
内置比较方式,而 uniqWith
允许开发者自定义比较逻辑,使其更加灵活。结果数组中元素的顺序由它们在原数组中首次出现的顺序决定。
前置学习
依赖函数
baseUniq
: 实现数组去重的基础函数,接收数组、迭代器和比较器参数。
技术知识
- 函数参数验证: 检查参数类型和有效性
- 函数式编程: 提供自定义比较器作为参数
- JavaScript 的数组操作
- 对象比较策略: 如何比较引用类型的值
源码实现
js
function uniqWith(array, comparator) {
comparator = typeof comparator == "function" ? comparator : undefined;
return array && array.length ? baseUniq(array, undefined, comparator) : [];
}
实现思路
uniqWith
的实现非常简洁,主要分为三个步骤:
- 验证
comparator
参数是否为函数,如果不是则设为undefined
- 检查
array
是否存在且有长度,如果是则进行去重处理 - 调用
baseUniq
进行实际的去重操作,传入array
、undefined
(表示不需要迭代器) 和comparator
核心去重逻辑由 baseUniq
函数实现,uniqWith
只是封装了参数验证和基础处理,使 API 更加清晰易用。
源码解析
我们来逐行分析 uniqWith
函数的源码:
js
function uniqWith(array, comparator) {
函数定义,接收两个参数:
array
: 要去重的数组comparator
: 用于比较数组元素的函数,接收两个参数 (arrVal, othVal)
js
comparator = typeof comparator == "function" ? comparator : undefined;
这行代码检查 comparator
参数的类型:
- 如果是函数类型,保持不变
- 如果不是函数类型,设置为
undefined
这种处理确保了传递给 baseUniq
的 comparator
要么是一个合法的函数,要么是 undefined
。不符合预期的比较器值(如非函数值)不会被错误地应用。
js
return array && array.length ? baseUniq(array, undefined, comparator) : [];
这行代码执行两个关键操作:
-
首先检查
array
是否存在且有长度(array && array.length
):- 如果条件为真,调用
baseUniq(array, undefined, comparator)
进行去重 - 如果条件为假(数组不存在或为空),直接返回空数组
[]
- 如果条件为真,调用
-
调用
baseUniq
时传递了三个参数:array
: 原始数组undefined
: 表示不使用迭代器(第二个参数为iteratee
,在uniqWith
中不需要)comparator
: 自定义比较器函数,用于判断两个元素是否相同
当我们调用 baseUniq
并传入比较器时,baseUniq
内部会做以下处理:
- 设置
isCommon = false
,表明我们正在使用非标准的比较方式 - 使用
arrayIncludesWith
而不是默认的arrayIncludes
作为包含检查函数,因为需要支持自定义比较器 - 对每个数组元素,使用比较器函数检查是否已在结果集中,避免重复
一个不那么直观但很重要的细节是,当使用比较器时,baseUniq
会跳过针对大数组的 Set
优化路径。这是因为 Set
使用内置的 SameValueZero
比较,无法应用自定义比较器。
示例分析
以官方示例为例:
js
var objects = [
{ x: 1, y: 2 },
{ x: 2, y: 1 },
{ x: 1, y: 2 },
];
_.uniqWith(objects, _.isEqual);
// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
处理流程如下:
- 验证
comparator
(这里是_.isEqual
) 是函数类型,保持不变 - 验证
array
存在且有长度,调用baseUniq
baseUniq
使用arrayIncludesWith
配合_.isEqual
进行比较- 对数组中的第三个对象
{ 'x': 1, 'y': 2 }
,使用_.isEqual
与已有结果比较 _.isEqual
发现它与第一个对象{ 'x': 1, 'y': 2 }
深度相等(即使它们是不同的引用)- 忽略这个重复元素,最终返回
[{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
如果没有使用 _.isEqual
而是使用普通的相等比较,则三个对象会被视为不同对象(因为它们是不同的引用),结果将保留所有三个对象。
总结
uniqWith
函数是 Lodash 中一个强大的数组去重工具,它的主要特点是:
- 灵活性:通过自定义比较器,可以根据任意规则判断元素是否相同,特别适合处理复杂对象
- 易用性:简单的 API 设计,只需传入数组和比较器即可
- 健壮性:对参数进行充分验证,确保函数能安全应用于各种输入情况
在实际开发中,uniqWith
常用于以下场景:
- 去除对象数组中"业务逻辑上"相同的对象,即使它们是不同的引用
- 实现自定义相等逻辑的去重,例如忽略某些属性或进行模糊匹配
- 结合
_.isEqual
等工具函数,实现深度相等比较的去重
从软件设计角度看,uniqWith
体现了单一职责原则和开放封闭原则。它专注于数组去重的单一职责,并通过比较器参数对不同去重策略开放,同时封装了内部实现细节,提供了一个简洁一致的 API。