ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

es-toolkit pullAllBy 详解:基于 iteratee 的原地数组批量删除(Lodash 兼容)

es-toolkit pullAllBy 详解:基于 iteratee 的原地数组批量删除(Lodash 兼容) es-toolkit pullAllBy 详解基于 iteratee 的原地数组批量删除Lodash 兼容【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkitpullAllBy是 es-toolkit 的 Lodash 兼容模块es-toolkit/compat提供的数组工具函数它会在原地修改传入的数组按 iteratee 转换后的值批量删除指定的元素并返回同一个数组引用。本文以 pullAllBy 官方参考文档 为主线结合 pullAllBy 源码实现 与 pullAllBy 测试用例完整讲解它的用法、参数、边界行为与底层原理并对比pullAll、pull、differenceBy等相近函数帮助你准确选型。快速上手一次调用完成按规则批量剔除pullAllBy的核心签名如下const modified pullAllBy(array, valuesToRemove, iteratee);array要修改的目标数组会被原地修改valuesToRemove要删除的值集合iteratee应用于每个元素的变换函数用于决定哪些值算作相同。官方文档给出的典型示例是按对象属性删除import { pullAllBy } from es-toolkit/compat; // Remove by comparing property values const array [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 1 }]; pullAllBy(array, [{ x: 1 }, { x: 3 }], x); console.log(array); // [{ x: 2 }]传入属性名x作为 iteratee 后pullAllBy会把数组元素与待删除值都变换成x属性值进行比较凡是x为1或3的元素全部被移除只剩下{ x: 2 }。它同样支持函数式 iteratee例如按奇偶性筛选// Compare by transforming values with a function const numbers [1, 2, 3, 4, 5]; pullAllBy(numbers, [2, 4], n n % 2); console.log(numbers); // [1, 3, 5] (only odd numbers remain)这里 iterateen n % 2将元素映射为 0 或 1待删除值2、4映射为 0因此原数组中所有偶数映射为 0 的值都被移除奇数保留。参数说明与返回值的完整约定参数参数类型是否可选说明arrayT[]必填要修改的数组valuesArrayLikeT可选要删除的值集合iterateeValueIterateeT可选应用到每个元素的 iteratee可以是属性名、部分对象或函数其中ValueIterateeT的定义位于 src/compat/_internal/ValueIteratee.ts展开为export type ValueIterateeT | ((value: T) unknown) | (PropertyKey | [PropertyKey, any] | PartialShallowT);也就是说iteratee 支持四种形式函数直接调用返回变换后的比较值属性名PropertyKey如x等价于obj obj.x属性-值对[PropertyKey, any]二元组做属性匹配部分对象PartialShallowT做对象部分匹配。返回值返回修改后的原数组本身同一引用而不是新数组。这意味着调用后原数组引用依然有效符合 Lodash 兼容语义。空值边界返回原样官方文档明确了空输入的处理如果数组是空数组、null或undefined函数原样返回import { pullAllBy } from es-toolkit/compat; pullAllBy([], [1, 2], x x); // [] pullAllBy(null as any, [1, 2], x x); // null这一点在源码中有一一对应实现见下文源码级原理并由 pullAllBy.spec.ts 中的should return the array as is when it is null or undefined用例验证。源码级原理Set 加速 原地重写理解了用法之后我们再深入 src/compat/array/pullAllBy.ts 的核心实现看看它究竟如何工作export function pullAllBy(arr: any, valuesToRemove: any, _getValue: any): any { if (arr?.length null || valuesToRemove?.length null) { return arr; } const getValue iteratee(_getValue); const valuesSet new Set(Array.from(valuesToRemove).map(x getValue(x))); let resultIndex 0; for (let i 0; i arr.length; i) { const value getValue(arr[i]); if (valuesSet.has(value)) { continue; } // For handling sparse arrays if (!Object.hasOwn(arr, i)) { delete arr[resultIndex]; continue; } arr[resultIndex] arr[i]; } arr.length resultIndex; return arr; }整个算法可以拆解为四个关键步骤第一步前置守卫。当arr?.length null数组为空、null或undefined或valuesToRemove?.length null待删除值缺失或为空时直接原样返回arr。这就是文档中空值返回原样行为的来源。注意这里用了 null同时覆盖null与undefined。第二步iteratee 统一化。通过iteratee(_getValue)将属性名、部分对象、函数等输入统一转换为函数。iteratee的转换逻辑位于 src/compat/util/iteratee.ts传入null/undefined→ 返回identity原样返回传入函数 → 原样返回传入对象 → 若为长度为 2 的数组则走matchesProperty属性-值对匹配否则走matches部分对象匹配传入其他字符串/数字/symbol→ 走property生成取属性函数。第三步构建待删除集合。用new Set()存储Array.from(valuesToRemove).map(x getValue(x))的结果。注意两个细节Array.from使得values参数支持一切ArrayLike结构包括类数组、字符串等使用Set做has判断把每个元素是否应删除的查询从 O(n) 降到 O(1)整体时间复杂度为 O(n m)n 为数组长度m 为待删除值数量空间复杂度 O(m)。第四步原地重写。用resultIndex作为写入游标单次循环内命中待删除集合的元素直接continue跳过未命中的元素前移写入arr[resultIndex]。循环结束后通过arr.length resultIndex截断数组尾部完成原地删除。值得一提的是对**稀疏数组sparse array**的处理当!Object.hasOwn(arr, i)时索引i没有自有属性代码用delete arr[resultIndex]保留空槽位语义而不是简单赋值。测试用例 pullAllBy.spec.ts 验证了这一点对[{ x: 1 }, { x: 2 }, , { x: 3 }, { x: 1 }]执行删除后Object.hasOwn(actual, 0)为true而Object.hasOwn(actual, 1)为false即稀疏结构被正确保留。测试用例如何锁定行为契约src/compat/array/pullAllBy.spec.ts 用 Vitest 全面覆盖了该函数的行为契约可作为阅读源码时的行为说明书iteratee 正确性pullAllBy(array, [{ x: 1 }, { x: 3 }], object object.x)得到[{ x: 2 }]iteratee 参数传递iteratee 被调用时收到的参数即当前元素本身args等于[{ x: 1 }]与 Lodash 行为一致稀疏数组删除过程保留空洞语义见上文空值安全null、undefined数组原样返回values省略、为null或undefined时数组不变非类数组 values 被忽略传入new Set([1])时数组原样返回——这与 Lodash 一致因为Set没有length属性会在前置守卫处直接短路。与相近函数的选型对比pullAllBy处于一个功能相近的函数族中理解差异有助于正确选型。与pullAll/pull的区别pullAllBy引入了 iteratee而它的无 iteratee 版本pullAll直接按元素值做 SameValueZero 相等比较。pullAll的实现见 src/compat/array/pullAll.ts底层复用了非兼容模块的 src/array/pull.ts 工具函数更基础的pullsrc/compat/array/pull.ts则把待删除值以可变参数形式传入。三者关系可概括为pull(array, 2, 3)按值删除可变参数pullAll(array, [2, 3])按值删除值收集为数组pullAllBy(array, [{x: 1}], x)按 iteratee 变换后的值删除。从源码注释看es-toolkit 明确提示pullAll之于pull正如pullAllBy之于pullAll即pullAllBy是pullAll的 iteratee 增强版。与differenceBy的区别原地修改 vs 返回新数组两者共享 iteratee 语义differenceBy的实现见 src/compat/array/differenceBy.ts但有一个关键差异pullAllBy原地修改传入数组并返回同一引用differenceBy返回新数组不修改原数组。pullAllBy的源码注释src/compat/array/pullAllBy.ts明确写道If you want to remove values without modifying the original array, usedifferenceBy. 如果你需要保留原始数据请务必选用differenceBy。从哪个入口导入pullAllBy从compat 兼容模块导出两种导入方式等价import { pullAllBy } from es-toolkit/compat;在包内它通过 src/compat/compat.ts 统一 re-export并同时暴露于 src/browser.ts浏览器构建入口。由于该函数属于 Lodash 兼容层命名与行为均对齐 Lodash方便从 Lodash 平滑迁移。总结pullAllBy是处理按规则批量删除数组元素的原地操作利器它支持函数、属性名、属性-值对、部分对象四种 iteratee 形式借助Set实现 O(n m) 的高效比较妥善处理稀疏数组与各类空值边界并保持 Lodash 兼容语义原地修改、返回原数组引用。需要保留原数组时请改用differenceBy。配合 pullAllBy 官方参考文档 与 源码实现 阅读可以快速掌握其全部行为边界。【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进