ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

es-toolkit 兼容版 without 数组剔除函数:用法、源码解析与性能取舍

es-toolkit 兼容版 without 数组剔除函数:用法、源码解析与性能取舍 es-toolkit 兼容版 without 数组剔除函数用法、源码解析与性能取舍【免费下载链接】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本文基于 es-toolkit 仓库中的 compat 版 without 参考文档 展开面向需要从数组中批量剔除指定值、同时追求 Lodash 行为兼容的开发者。读完本文你将掌握without的完整调用方式、与原生版without的差异、底层实现原理SameValueZero 比较、-0归一化、类数组支持并学会在何时选择 compat 版、何时改用更快的原生版。一、without 是什么without创建一个新数组剔除其中所有与给定值相等的元素。它不会修改原数组属于典型的纯函数式数组工具。const result without([1, 2, 3, 4, 5], 2, 4); // result is [1, 3, 5].在 es-toolkit 中该函数存在两个入口原生版从es-toolkit/array导入对应 docs/reference/array/without.md追求性能兼容版从es-toolkit/compat导入对应本文主题在 Lodash 兼容性上做额外处理因此官方文档明确提示其operates slowly due to additional processing for Lodash compatibility见 docs/compat/reference/array/without.md 顶部警告块。二、compat 版 without 的用法函数签名without(array, ...values)从array中剔除所有...values指定的值返回新数组原数组保持不变。参数arrayT[]待处理的原始数组。...valuesT[]需要从数组中移除的值支持任意多个。返回值T[]移除指定值之后的新数组。基础示例import { without } from es-toolkit/compat; // 从数字数组中移除多个值 const numbers [1, 2, 3, 4, 5, 2, 4]; const result1 without(numbers, 2, 4); // Returns: [1, 3, 5] // 从字符串数组中移除指定值所有匹配项都会被移除 const fruits [apple, banana, cherry, banana]; const result2 without(fruits, banana); // Returns: [apple, cherry] // 空数组场景 const result3 without([], 1, 2, 3); // Returns: []注意第二个示例banana在原数组中出现了两次without会将其所有出现都移除这与 Lodash 行为一致也由 src/compat/array/without.spec.ts 中的测试should remove all occurrences of each value from an array所验证。三、源码级解析compat 版如何工作compat 版实现位于 src/compat/array/without.ts完整代码如下import { normalizeZero } from ../_internal/normalizeZero.ts; import { isArrayLikeObject } from ../predicate/isArrayLikeObject.ts; export function withoutT(array: ArrayLikeT | null | undefined, ...values: T[]): T[] { if (!isArrayLikeObject(array)) { return []; } const valuesSet new Set(values); const result: T[] []; for (let i 0; i array.length; i) { const value array[i]; if (!valuesSet.has(value)) { result.push(normalizeZero(value)); } } return result; }其执行流程可分为三步类型守卫通过isArrayLikeObject见 src/compat/predicate/isArrayLikeObject.ts判断入参是否为类数组对象。若不是如null、undefined、数字、布尔值直接返回空数组[]避免后续遍历报错建立查找集合把values放入Set使每次成员判断的复杂度降到 O(1)整体时间复杂度为 O(n)遍历过滤遍历原数组凡是不在集合中的值经normalizeZero处理后推入结果数组。兼容版之所以比原生版慢正是因为它需要兼容 Lodash 的多种边界行为见下文第四节多出的类型判断与逐元素处理开销无法避免。四、Lodash 兼容行为与原生版的差异点这是 compat 版存在的核心价值也是官方文档提醒慢的原因所在。1. SameValueZero 比较正确剔除 NaNSet.prototype.has底层使用 ECMAScript 的 SameValueZero 抽象运算因此NaN能正确匹配NaN自身。源码注释src/compat/array/without.ts明确说明这一点without([1, NaN, 3, NaN, 5], NaN); // Returns: [1, 3, 5]原生版同样继承了这一特性其底层difference同样使用Set详情见 src/array/difference.ts。2. -0 归一化为 0Lodash 在处理uniq、union、difference等函数的结果时会把-0归一化为0。compat 版通过normalizeZero见 src/compat/_internal/normalizeZero.ts复刻该行为import { normalizeZero } from ../_internal/normalizeZero.ts; export function normalizeZeroT(value: T): T { return value 0 ? (0 as T) : value; }对应的测试src/compat/array/without.spec.ts验证了expect(without([-0, 1], 1)).toEqual([0]); expect(without([-0])).toEqual([0]);注意这条规则只对保留在结果中的值生效-0作为待剔除值传入时由于 SameValueZero 认为-0 0同样能正常匹配剔除。3. 引用相等对象按引用而非结构比较剔除对象值时without使用严格相等语义——只有同一个引用才能被匹配结构相同的不同对象不会被剔除。测试src/compat/array/without.spec.ts给出了明确验证const object1 { a: 1 }; const object2 { b: 2 }; const array [object1, object2]; without(array, { a: 1 }); // 返回 [object1, object2]结构相同但不剔除 without(array, object1); // 返回 [object2]引用相同才剔除4. 类数组对象支持compat 版接受ArrayLikeT即只要对象拥有length属性和下标访问能力即可包括普通对象、字符串、arguments对象。测试src/compat/array/without.spec.ts验证without({ 0: 1, 1: 2, 2: 3, length: 3 }, 1, 2); // [3] without(123, 1, 2); // []字符串按字符遍历 without(args, 1, 2); // [3]5. null / undefined 与非类数组输入当传入null、undefined或非类数组值如数字、布尔值时返回空数组src/compat/array/without.spec.tswithout(null, 1, 2); // [] without(1, 2); // []ts-expect-error 标注的非法入参 without(true, 2); // []五、性能提示优先使用原生版 withoutcompat 版为兼容 Lodash 行为付出了额外开销官方文档在开头用警告块明确建议除非必须迁移 Lodash 代码否则应使用更快的原生版。原生版实现极简src/array/without.ts直接委托给differenceimport { difference } from ./difference.ts; export function withoutT(array: readonly T[], ...values: T[]): T[] { return difference(array, values); }而difference只是一行Setfiltersrc/array/difference.tsexport function differenceT(firstArr: readonly T[], secondArr: readonly T[]): T[] { const secondSet new Set(secondArr); return firstArr.filter(item !secondSet.has(item)); }两者的取舍建议场景推荐版本理由新项目、纯数组场景es-toolkit/array的原生版无兼容开销实现更短语义等价从 Lodash 迁移、需要-0归一化或类数组/null容错es-toolkit/compat的兼容版行为与 Lodash 对齐迁移零改动输入可能是null、类数组对象、需要 Lodash 精确语义es-toolkit/compat的兼容版原生版要求readonly T[]不做容错从源码结构看compat 版实际是原生版 兼容层的组合在需要 Lodash 精确行为-0归一化、类数组支持、空输入容错时选择 compat在追求极致性能且输入可靠时选择原生。如果你的代码库同时存在两种需求建议分别从es-toolkit/compat与es-toolkit/array导入并让性能敏感路径使用原生版。【免费下载链接】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

延伸阅读

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