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。

相关推荐
cypking22 分钟前
Vue 3 + Vite + Router + Pinia + Element Plus + Monorepo + qiankun 构建企业级中后台前端框架
前端·javascript·vue.js
San30.32 分钟前
ES6+ 新特性解析:让 JavaScript 开发更优雅高效
开发语言·javascript·es6
雨雨雨雨雨别下啦1 小时前
【从0开始学前端】vue3简介、核心代码、生命周期
前端·vue.js·vue
simon_93491 小时前
受够了压缩和收费?我作为一个码农,手撸了一款无限容量、原图直出的瀑布流相册!
前端
e***87702 小时前
windows配置永久路由
android·前端·后端
u***27612 小时前
TypeScript 与后端开发Node.js
javascript·typescript·node.js
星空的资源小屋2 小时前
跨平台下载神器ArrowDL,一网打尽所有资源
javascript·笔记·django
Dorcas_FE3 小时前
【tips】动态el-form-item中校验的注意点
前端·javascript·vue.js
小小前端要继续努力3 小时前
前端新人怎么更快的融入工作
前端
八月ouc3 小时前
解密JavaScript模块化演进:从IIFE到ES Module,深入理解现代前端工程化基石
javascript·es6·模块化·cmd·commonjs·amd·iife