news 2026/9/16 15:47:15

es-toolkit 兼容版 differenceBy:基于迭代器转换的差集计算完全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
es-toolkit 兼容版 differenceBy:基于迭代器转换的差集计算完全指南

es-toolkit 兼容版 differenceBy:基于迭代器转换的差集计算完全指南

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

differenceBy是 es-toolkit 的 Lodash 兼容模块(es-toolkit/compat)中提供的高阶差集函数:它先用迭代器(iteratee)把每个元素转换为比较基准值,再从第一个数组中剔除在其他数组中"转换后值相同"的元素。本文以 docs/ja/compat/reference/array/differenceBy.md 为骨架,结合仓库源码与测试用例,讲解其完整用法、参数语义、实现原理与边界行为,读完即可在迁移 Lodash 或做数据清洗时直接上手。

一、函数定位与适用场景

const result = differenceBy(array, ...values, iteratee);

differenceBy解决的核心问题是"按转换后的值做差集":直接比较原始值往往不符合业务语义,例如2.12.3在按Math.floor转换后都是2,应视为相同。此时用它就能以"某个属性、字符串长度或任意映射结果"为基准求差集,非常适合:

  • 对象数组按特定属性去重/求差:如按id比较两个用户列表;
  • 按派生值比较:如按字符串长度、按取整后的数值比较;
  • 需要 Lodash 行为兼容的存量项目:函数签名、-0归一化、NaN匹配等细节与 Lodash 保持一致。

需要特别注意的是,文档开头给出了明确的性能提示:兼容版differenceBy因复杂的参数处理和迭代器转换,运行较慢,建议优先使用 es-toolkit 原生的快速版 differenceBy(源码位于 src/array/differenceBy.ts)。兼容版的价值在于"行为兼容、无缝迁移",追求极致性能时则应切换到原生版本。

二、安装与导入

兼容版函数从es-toolkit/compat子路径导入:

import { differenceBy } from 'es-toolkit/compat';

compat模块的完整入口定义在 src/compat/index.ts 与 src/compat/compat.ts,其设计目标是与 Lodash 保持 API 与行为兼容,便于项目无痛迁移。

三、核心用法详解

3.1 使用函数迭代器:按转换结果比较

最直接的形式是传入一个映射函数,先转换再比较:

import { differenceBy } from 'es-toolkit/compat'; // 小数点以下切り捨てで比較(按向下取整结果比较) differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); // Returns: [1.2] (Math.floor(2.1) === Math.floor(2.3) ため2.1を除外) // 说明:2.1 与 2.3 取整后同为 2,因此 2.1 被剔除,仅保留 1.2

这里Math.floor同时作用于第一个数组的元素和所有待排除数组的元素,转换后值相等的元素从结果中剔除。

3.2 使用属性名迭代器:按属性比较

第二个参数可以传字符串属性名,此时会提取每个元素的该属性值作为比较基准:

import { differenceBy } from 'es-toolkit/compat'; // 按字符串长度比较 differenceBy(['one', 'two', 'three'], ['four', 'eight'], 'length'); // Returns: ['one', 'two'] // 说明:'three' 与 'eight' 长度都是 5,因此 'three' 被剔除 // 按对象 id 属性比较 const users1 = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ]; const users2 = [{ id: 1, name: 'Different Alice' }]; differenceBy(users1, users2, 'id'); // Returns: [{ id: 2, name: 'Bob' }] // 说明:id 为 1 的对象(即使 name 不同)被剔除

属性名迭代器让"按对象数组的某个字段求差集"变得一行即可完成。

3.3 一次排除多个数组

...values支持任意数量的待排除数组,且每个数组都会先经迭代器转换再参与比较:

import { differenceBy } from 'es-toolkit/compat'; // 多个数组同时排除 differenceBy([2.1, 1.2, 3.5], [2.3], [1.4], [3.2], Math.floor); // Returns: [] (取整后 2、1、3 全部被覆盖,所有元素均被剔除) // 字符串数组按长度比较 differenceBy(['a', 'bb', 'ccc'], ['x'], ['yy'], ['zzz'], 'length'); // Returns: [] (长度 1、2、3 均被覆盖)

从源码看,待排除数组会被拍平合并后再统一转换比较(见下文"实现原理")。

3.4 不传迭代器:退化为 difference

当最后一个参数是数组(而非函数、属性名等)时,differenceBy等价于普通差集:

import { differenceBy } from 'es-toolkit/compat'; // 不传迭代器 differenceBy([1, 2, 3], [2, 4]); // Returns: [1, 3]

这一点在测试 src/compat/array/differenceBy.spec.ts 中也有验证:differenceBy([2, 1, 2, 3], [3, 4], [3, 2])返回[1]

3.5 空值与空数组处理

nullundefined作为第一个数组时,一律返回空数组:

import { differenceBy } from 'es-toolkit/compat'; differenceBy(null, [1, 2], Math.floor); // Returns: [] differenceBy(undefined, [1, 2], x => x); // Returns: []

对应实现见 src/compat/array/differenceBy.ts:入口处先通过isArrayLikeObject检查,非类数组对象直接返回[]。测试 src/compat/array/differenceBy.spec.ts 进一步证明字符串'23'作为首个参数也会返回[]

四、参数与返回值规范

项目类型说明
arrayArrayLike<T> \| null \| undefined求差集的基准数组,可为类数组对象;非类数组或空值返回[]
...valuesArray<ArrayLike<T>>待排除元素所在的一个或多个数组
iterateeValueIteratee<T>将每个元素转换为比较值的迭代器,支持函数、属性名、属性值对或部分对象
返回值T[]剔除"转换后值相同"元素后的新数组,不修改原数组

其中ValueIteratee<T>的类型定义位于 src/compat/_internal/ValueIteratee.ts:

export type ValueIteratee<T> = ((value: T) => unknown) | (PropertyKey | [PropertyKey, any] | PartialShallow<T>);

即迭代器可以是:函数、属性键(PropertyKey,即字符串/数字/symbol)、[属性键, 期望值]二元组、或部分对象。

五、迭代器转换机制(源码级原理)

兼容版differenceBy会把最后一个参数当作迭代器,交给统一的迭代器工厂createIteratee处理。工厂函数实现在 src/compat/util/iteratee.ts,转换规则如下:

传入的迭代器转换结果说明
null/undefinedidentity原样返回输入,等价于不转换
函数原函数直接使用,如Math.floor
二元数组['a', 1]matchesProperty('a', 1)判断元素属性a是否等于1
对象matches(obj)判断元素是否匹配该部分对象
其他(属性键)property(value)提取元素对应属性值,如'length''id'

这解释了为什么'length''id'这类字符串能直接作为迭代器使用,也意味着兼容版支持 Lodash 风格的['a', 1]属性值对写法。配套的matchesPropertymatchesproperty实现分别位于 src/compat/predicate/matchesProperty.ts、src/compat/predicate/matches.ts、src/compat/object/property.ts。

六、兼容版实现流程与边界行为

6.1 主实现流程

兼容版函数体位于 src/compat/array/differenceBy.ts,执行步骤如下:

  1. 空值防护isArrayLikeObject(array)为假则直接返回[]
  2. 提取迭代器last(_values)取最后一个参数作为迭代器;
  3. 拍平待排除数组flattenArrayLike(_values)将多个类数组参数拍平为单一数组,实现见 src/compat/_internal/flattenArrayLike.ts(跳过其中非类数组的值);
  4. 判断分支
    • 若迭代器本身是类数组对象(说明没传迭代器),调用原生 difference 求普通差集;
    • 否则调用原生differenceBy并传入createIteratee(iteratee)转换后的迭代器;
  5. 结果归一化:对每个结果元素执行normalizeZero,把-0归一为0,对齐 Lodash 行为(见 src/compat/_internal/normalizeZero.ts)。

6.2 底层原生实现:基于 Set 的 O(n) 算法

无论走哪个分支,最终都由 es-toolkit 原生实现完成核心计算。原生differenceBy(src/array/differenceBy.ts)的算法非常简洁高效:

  1. 对第二个数组逐元素应用mapper,构建Set(利用 Set 的哈希查找);
  2. 遍历第一个数组,对每个元素应用mapper,若映射结果不在 Set 中则保留。
const mappedSecondSet = new Set(secondArr.map(item => mapper(item))); return firstArr.filter(item => { return !mappedSecondSet.has(mapper(item)); });

由于Set.has是常数级查找,整体时间复杂度为 O(n),这也是原生版性能优于兼容版(兼容版额外承担了参数解析、数组拍平与迭代器转换开销)的根本原因。原生difference(src/array/difference.ts)采用同样的 Set 策略。

6.3 测试覆盖的边界行为

src/compat/array/differenceBy.spec.ts 完整覆盖了以下 Lodash 兼容语义,可作为行为契约参考:

  • -0归一化为0differenceBy([-0, 1], [1])返回[0](L48-L56),并在显式传迭代器时同样生效(L128-L130);
  • NaN匹配differenceBy([1, NaN, 3], [NaN, 5, NaN])返回[1, 3],说明NaN能被正确识别为相同值(L58-L60);
  • 大型数组:以LARGE_ARRAY_SIZE规模验证性能与正确性(L62-L74);
  • arguments与类数组对象{ 0: 1, 1: 2, length: 2 }这类类数组可直接作为基准或排除来源(L104-L117);
  • 非数组值过滤:排除参数中的非类数组值(如字符串'2')会被跳过,不会破坏结果(L123-L126)。

6.4 类型重载

为了在 TypeScript 下精确推导返回类型,兼容版对 1 到 6 个排除数组分别声明了重载(T1~T6泛型),第 7 个起落入剩余参数重载,见 src/compat/array/differenceBy.ts。这意味着对T1[]T2[]混合求差集时,返回类型始终是第一个数组的元素类型T1[]

七、与原生 differenceBy 的选型建议

维度es-toolkit/compat兼容版es-toolkit 原生版
导入路径es-toolkit/compates-toolkit
迭代器形式函数 / 属性名 / 属性值对 / 部分对象仅函数(mapper)
多数组排除支持任意数量仅支持两个数组
-00归一化支持(对齐 Lodash)不支持
空值 / 非类数组参数安全返回[]由调用方自行保证
性能较慢(参数解析 + 迭代器转换开销)更快(纯 Set 算法)

结论:正在从 Lodash 迁移、需要严格行为兼容的代码,用es-toolkit/compatdifferenceBy;新代码追求性能与简洁,直接用 es-toolkit 原生 differenceBy(对应源码 src/array/differenceBy.ts)。如需进一步了解兼容模块的整体设计,可阅读 compat 模块说明。

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 15:46:43

STM32模拟SPI驱动TLC3578实现8路±10V高精度采集

简介&#xff1a;针对STM32开发者&#xff0c;这份资源围绕TLC3578模数转换芯片&#xff0c;提供基于模拟SPI的驱动方案&#xff0c;解决8通道10V信号采集与单片机通信问题。资源共152个文件&#xff0c;整体4.35MB&#xff0c;包含H/C源文件、Keil工程文件、PDF说明文档、编译…

作者头像 李华
网站建设 2026/9/16 15:46:36

MATLAB中SA-PSO混合优化算法原理与神经网络训练实战

简介&#xff1a;本资源是一套面向MATLAB初学者与优化算法实践者的融合型算法实现代码包&#xff0c;聚焦于智能优化领域中模拟退火&#xff08;SA&#xff09;与粒子群&#xff08;PSO&#xff09;算法的协同改进思路&#xff0c;适用于高校课程设计、毕业设计及科研原型验证等…

作者头像 李华
网站建设 2026/9/16 15:46:09

用 mcp-agent 构建带日志与进度通知能力的 MCP Server 实战指南

用 mcp-agent 构建带日志与进度通知能力的 MCP Server 实战指南 【免费下载链接】mcp-agent Build effective agents using Model Context Protocol and simple workflow patterns 项目地址: https://gitcode.com/GitHub_Trending/mc/mcp-agent 导读 本篇基于 mcp-agen…

作者头像 李华
网站建设 2026/9/16 15:44:59

AI短漫剧工业化生产全链路方案解析

1. 项目概述&#xff1a;这不是“用AI画几张图”&#xff0c;而是一整套工业化短漫剧流水线“腾讯云AIGC全链路方案&#xff1a;降低AI短漫剧制作成本并提升产能”——这个标题里藏着三个被很多人忽略的关键词&#xff1a;全链路、工业化、短漫剧。不是单点工具&#xff0c;不是…

作者头像 李华
网站建设 2026/9/16 15:43:59

Springboot+Vue智能推荐卫生健康系统设计与部署实践指南

作为一名带过不少毕业设计、也帮人排查过无数SpringbootVue项目的过来人&#xff0c;我第一眼看到“基于SpringbootVue的智能推荐的卫生健康系统源码文档部署文档代码讲解等”这个标题&#xff0c;就知道这又是个典型的全栈“大综合”项目。这类项目在高校毕业设计、课程设计里…

作者头像 李华