Lodash源码阅读-uniqWith

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 的实现非常简洁,主要分为三个步骤:

  1. 验证 comparator 参数是否为函数,如果不是则设为 undefined
  2. 检查 array 是否存在且有长度,如果是则进行去重处理
  3. 调用 baseUniq 进行实际的去重操作,传入 arrayundefined (表示不需要迭代器) 和 comparator

核心去重逻辑由 baseUniq 函数实现,uniqWith 只是封装了参数验证和基础处理,使 API 更加清晰易用。

源码解析

我们来逐行分析 uniqWith 函数的源码:

js 复制代码
function uniqWith(array, comparator) {

函数定义,接收两个参数:

  • array: 要去重的数组
  • comparator: 用于比较数组元素的函数,接收两个参数 (arrVal, othVal)
js 复制代码
comparator = typeof comparator == "function" ? comparator : undefined;

这行代码检查 comparator 参数的类型:

  • 如果是函数类型,保持不变
  • 如果不是函数类型,设置为 undefined

这种处理确保了传递给 baseUniqcomparator 要么是一个合法的函数,要么是 undefined。不符合预期的比较器值(如非函数值)不会被错误地应用。

js 复制代码
return array && array.length ? baseUniq(array, undefined, comparator) : [];

这行代码执行两个关键操作:

  1. 首先检查 array 是否存在且有长度(array && array.length):

    • 如果条件为真,调用 baseUniq(array, undefined, comparator) 进行去重
    • 如果条件为假(数组不存在或为空),直接返回空数组 []
  2. 调用 baseUniq 时传递了三个参数:

    • array: 原始数组
    • undefined: 表示不使用迭代器(第二个参数为 iteratee,在 uniqWith 中不需要)
    • comparator: 自定义比较器函数,用于判断两个元素是否相同

当我们调用 baseUniq 并传入比较器时,baseUniq 内部会做以下处理:

  1. 设置 isCommon = false,表明我们正在使用非标准的比较方式
  2. 使用 arrayIncludesWith 而不是默认的 arrayIncludes 作为包含检查函数,因为需要支持自定义比较器
  3. 对每个数组元素,使用比较器函数检查是否已在结果集中,避免重复

一个不那么直观但很重要的细节是,当使用比较器时,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 }]

处理流程如下:

  1. 验证 comparator (这里是 _.isEqual) 是函数类型,保持不变
  2. 验证 array 存在且有长度,调用 baseUniq
  3. baseUniq 使用 arrayIncludesWith 配合 _.isEqual 进行比较
  4. 对数组中的第三个对象 { 'x': 1, 'y': 2 },使用 _.isEqual 与已有结果比较
  5. _.isEqual 发现它与第一个对象 { 'x': 1, 'y': 2 } 深度相等(即使它们是不同的引用)
  6. 忽略这个重复元素,最终返回 [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]

如果没有使用 _.isEqual 而是使用普通的相等比较,则三个对象会被视为不同对象(因为它们是不同的引用),结果将保留所有三个对象。

总结

uniqWith 函数是 Lodash 中一个强大的数组去重工具,它的主要特点是:

  1. 灵活性:通过自定义比较器,可以根据任意规则判断元素是否相同,特别适合处理复杂对象
  2. 易用性:简单的 API 设计,只需传入数组和比较器即可
  3. 健壮性:对参数进行充分验证,确保函数能安全应用于各种输入情况

在实际开发中,uniqWith 常用于以下场景:

  1. 去除对象数组中"业务逻辑上"相同的对象,即使它们是不同的引用
  2. 实现自定义相等逻辑的去重,例如忽略某些属性或进行模糊匹配
  3. 结合 _.isEqual 等工具函数,实现深度相等比较的去重

从软件设计角度看,uniqWith 体现了单一职责原则和开放封闭原则。它专注于数组去重的单一职责,并通过比较器参数对不同去重策略开放,同时封装了内部实现细节,提供了一个简洁一致的 API。

相关推荐
weifont1 小时前
聊一聊Electron中Chromium多进程架构
javascript·架构·electron
大得3691 小时前
electron结合vue,直接访问静态文件如何跳转访问路径
javascript·vue.js·electron
水银嘻嘻3 小时前
12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建
运维·前端·自动化
it_remember3 小时前
新建一个reactnative 0.72.0的项目
javascript·react native·react.js
小嘟嚷ovo4 小时前
h5,原生html,echarts关系网实现
前端·html·echarts
十一吖i4 小时前
Vue3项目使用ElDrawer后select方法不生效
前端
只可远观4 小时前
Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件
前端·flutter
周胡杰4 小时前
组件导航 (HMRouter)+flutter项目搭建-混合开发+分栏效果
前端·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
敲代码的小吉米5 小时前
前端上传el-upload、原生input本地文件pdf格式(纯前端预览本地文件不走后端接口)
前端·javascript·pdf·状态模式
是千千千熠啊5 小时前
vue使用Fabric和pdfjs完成合同签章及批注
前端·vue.js