Lodash 源码阅读-uniqBy
概述
uniqBy
函数用于创建一个数组的去重版本,通过指定的迭代器函数为数组的每个元素生成用于比较的标准。它只保留数组中每个元素(基于迭代器返回值)的第一次出现。结果数组中元素的顺序取决于它们在原数组中首次出现的位置。
前置学习
依赖函数
baseUniq
:内部函数,实现数组去重的核心逻辑getIteratee
:获取适当的迭代器函数
技术知识
- 数组去重算法
- 高阶函数:接收函数作为参数
- 迭代器函数:为每个数组元素生成比较标准
- JavaScript 中的 SameValueZero 比较
- 函数柯里化和函数式编程
源码实现
javascript
function uniqBy(array, iteratee) {
return array && array.length ? baseUniq(array, getIteratee(iteratee, 2)) : [];
}
实现思路
uniqBy
函数的实现非常简洁,但功能强大。它首先检查输入数组是否存在且有长度,如果是,则调用内部的 baseUniq
函数进行实际的去重操作;否则返回空数组。
在调用 baseUniq
时,它传入了两个参数:
- 原始数组
array
- 通过
getIteratee(iteratee, 2)
获取的迭代器函数,其中2
表示迭代器函数应接收一个参数(数组元素)
uniqBy
的核心在于允许用户自定义如何生成比较元素唯一性的标准,这使得去重过程更加灵活和强大。
源码解析
让我们逐行分析 uniqBy
函数的实现:
javascript
function uniqBy(array, iteratee) {
函数定义,接收两个参数:
array
:要去重的数组iteratee
:迭代器函数,为每个元素生成比较的键值
javascript
return array && array.length ? baseUniq(array, getIteratee(iteratee, 2)) : [];
这行代码完成了主要工作:
array && array.length
检查数组是否存在且有长度- 如果条件为真,调用
baseUniq
函数- 第一个参数是原始数组
array
- 第二个参数是通过
getIteratee(iteratee, 2)
获取的迭代器函数
- 第一个参数是原始数组
- 如果条件为假(数组不存在或为空),返回空数组
[]
getIteratee(iteratee, 2)
是个关键部分,它根据传入的 iteratee
类型返回一个适当的函数:
- 如果
iteratee
是函数,它会被直接使用 - 如果
iteratee
是字符串,它会被转换为属性访问函数(如_.property
的效果) - 如果
iteratee
是对象,它会被转换为匹配器函数(如_.matches
的效果) - 如果
iteratee
是数组,它会被转换为属性-值匹配函数(如_.matchesProperty
的效果) - 如果
iteratee
为 null 或 undefined,会返回 identity 函数(返回原值)
参数 2
表示返回的迭代器函数的参数数量 (arity),这里指定为接收一个参数。
当 baseUniq
执行时,它会使用这个迭代器函数为每个数组元素计算一个 "键",然后使用这些键来确定元素是否唯一。相同键的元素中,只有第一个会被保留在结果数组中。
总结
uniqBy
函数是 Lodash 的一个实用工具,通过允许自定义比较标准,它提供了比简单的 uniq
函数更强大的数组去重能力。
这个函数的主要特点包括:
- 灵活性:支持多种形式的迭代器(函数、字符串路径、对象匹配器等)
- 保留原始值:虽然比较是基于迭代器返回的值,但结果数组中保留的是原始元素
- 保持顺序:保留了元素在原数组中的相对顺序(首次出现的位置)
- 参数验证:优雅地处理边缘情况,如空数组或 null 值
在实际应用中,uniqBy
特别适用于:
- 去除具有相同特定属性值的对象
- 基于复杂计算进行去重
- 实现类似 SQL 中的 "GROUP BY" 功能(取每组第一个元素)
从设计模式角度看,uniqBy
使用了策略模式(允许用户选择不同的去重策略)和委托模式(将具体实现委托给内部函数)。这种设计让 API 保持简洁易用,同时内部实现可以更加复杂和高效。