es-toolkit 的 omitBy 兼容实现解析:按谓词动态过滤对象属性的完整指南
2026/9/16 11:46:25 网站建设 项目流程

es-toolkit 的 omitBy 兼容实现解析:按谓词动态过滤对象属性的完整指南

【免费下载链接】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

omitBy是 es-toolkit 提供的对象属性过滤工具:对对象的每个属性执行谓词函数,凡是谓词返回true的属性都会被剔除,最终返回一个只包含"不满足条件"属性的新对象。本文以 compat 兼容版文档 为核心骨架,结合 src/compat/object/omitBy.ts 源码与 src/compat/object/omitBy.spec.ts 测试用例,完整讲解其用法、参数语义、边界行为、底层实现原理,并与更快的现代版es-toolkit/object进行对比,帮助你按需选择正确的 API。

注意:本文讲解的omitBy来自es-toolkit/compat入口,旨在提供与 Lodash 行为高度一致的兼容实现;如果你不需要 Lodash 兼容语义,官方强烈推荐使用更快、更现代的es-toolkit/object版本(见文末对比小节)。

一、omitBy 解决什么问题

在业务开发中,经常需要"按条件剔除属性":比如删除对象中所有字符串类型的值、剔除所有空字符串、按 key 前缀过滤配置项等。omitBy正是为此设计的——它接收一个源对象和一个谓词函数(predicate),遍历对象每个属性,将谓词返回true的属性从结果中剔除:

const result = omitBy(obj, predicate);

pickBy(保留满足条件的属性)相反,omitBy保留的是不满足条件的属性,二者互为镜像。它在需要根据运行时条件动态过滤对象时非常有用,无需预先枚举要删除的 key。

二、安装与导入

本项目是 monorepo 形态的开源仓库,使用 Yarn 管理依赖(见仓库根目录 package.json 与yarn.lock)。在业务项目中安装 es-toolkit 后,按入口导入:

// 兼容版(Lodash 互換,语义与 Lodash 对齐) import { omitBy } from 'es-toolkit/compat'; // 现代版(更快的原生实现) import { omitBy } from 'es-toolkit/object';

两条入口都导出了同名omitBy,但行为存在差异(见第五节与第九节),请根据实际需求选择。

三、基础用法:五个典型场景

原文档给出了 5 个覆盖不同谓词形态的示例,全部继承如下:

3.1 删除特定类型的值

import { omitBy } from 'es-toolkit/compat'; const data = { a: 1, b: 'remove', c: 3, d: 'keep' }; const numbers = omitBy(data, value => typeof value === 'string'); // 结果: { a: 1, c: 3 }

3.2 按真值条件删除(剔除所有假值)

const user = { id: 1, name: 'John', age: 0, active: false, email: '' }; const validData = omitBy(user, value => !value); // 结果: { id: 1, name: 'John' }(age: 0、active: false、email: '' 均为假值,被剔除)

3.3 按 key 名过滤

谓词除了接收 value,还会接收属性名 key,因此可以基于 key 做过滤:

const settings = { userSetting: true, adminSetting: false, debugMode: true }; const userOnly = omitBy(settings, (value, key) => key.startsWith('admin')); // 结果: { userSetting: true, debugMode: true }

3.4 混合对象中剔除数值属性

const mixed = { str: 'hello', num1: 42, bool: true, num2: 0, obj: {} }; const noNumbers = omitBy(mixed, value => typeof value === 'number'); // 结果: { str: 'hello', bool: true, obj: {} }

3.5 对数组使用

数组是特殊的对象(索引为 key),omitBy同样适用,结果会以"索引字符串为 key"的对象形式返回:

const arr = [1, 2, 3, 4, 5]; const filtered = omitBy(arr, value => value % 2 === 0); // 结果: { '0': 1, '2': 3, '4': 5 }(值为奇数、索引为 '0'/'2'/'4' 的属性被保留)

3.6 同时使用 value、key 与源对象

虽然类型签名只声明了两个参数,但兼容版实现实际会以三个参数调用谓词(见源码 L95:predicate(value, key, object)),因此你可以在谓词中拿到原始源对象做统计或对照:

const scores = { math: 90, science: 75, english: 85, art: 60 }; const passingGrades = omitBy(scores, (value, key, obj) => { console.log(`${key}: ${value} (平均: ${Object.values(obj).reduce((a, b) => a + b) / Object.keys(obj).length})`); return value < 80; }); // 结果: { math: 90, english: 85 }

四、边界处理:null 与 undefined

omitBynullundefined源对象采取宽容策略——将其视为空对象处理,返回{}而不是抛错:

import { omitBy } from 'es-toolkit/compat'; omitBy(null, () => true); // {} omitBy(undefined, () => true); // {}

这一点在源码中体现得非常直接:实现的第一行就是空值短路(src/compat/object/omitBy.ts):

if (object == null) { return {}; }

注意这里用的是宽松相等== null,因此同时覆盖了nullundefined两种情况。对应的测试用例位于 omitBy.spec.ts:当源对象为null时,无论谓词为何,结果均为{}

五、参数与返回值详解

参数

参数类型说明
objectRecord<string, T> \| Record<number, T> \| object \| null \| undefined待过滤的源对象,支持字符串键对象、数值键对象、数组、普通对象,以及null/undefined
predicateValueKeyIteratee<T[keyof T]> \| ValueKeyIteratee<T>(可选)对每个属性执行的谓词函数,返回true的属性将被剔除;默认值为identity函数(即原样返回输入值)

predicate是可选的。若省略,会退化为默认的identity谓词——此时等价于"保留所有假值属性、剔除所有真值属性"。这一默认值在实现中通过createIteratee(shouldOmit ?? identity)生效(src/compat/object/omitBy.ts),identity定义于 src/compat/function/identity.ts。

测试中专门覆盖了该场景:omitBy({ a: 1, b: 'omit', c: 3 }, null)返回{}(omitBy.spec.ts)——因为null谓词被?? identity兜底为恒等函数后,三个属性的值均为真值,于是全部被剔除。

返回值

Record<string, S> | Record<number, S> | Partial<T>:由所有"谓词返回 false(不满足条件)"的属性构成的新对象。函数不会修改原对象,始终返回全新的对象。

谓词的类型:ValueKeyIteratee

兼容版的谓词类型来自ValueKeyIteratee<T>(定义于 src/compat/_internal/ValueKeyIteratee.ts):

export type ValueKeyIteratee<T> = ((value: T, key: string) => unknown) | IterateeShorthand<T>;

它可以是:

  • 函数(value, key) => unknown,最常用;
  • IterateeShorthand 简写PropertyKey | [PropertyKey, any] | PartialShallow<T>,即属性名、[属性名, 值]二元组或部分对象。

这得益于实现内部统一通过createIteratee(即 src/compat/util/iteratee.ts 中的iteratee转换器)把任意形态的谓词归一为函数,转换规则如下:

  • 函数:原样返回;
  • 属性名 / Symbol / 数值 key:转换为property(key),即"取出该属性的值";
  • [属性名, 值]二元组:转换为matchesProperty,即"判断该属性是否等于指定值";
  • 普通对象:转换为matches,即"判断源对象是否匹配该部分对象";
  • null/undefined:返回identity

因此,omitBy的谓词既可以是回调函数,也可以直接传入'active'['type', 'admin']{ status: 'disabled' }这类 Lodash 风格简写——这正是兼容版与 Lodash 语义对齐的体现。

六、源码实现深度解析:兼容版做了什么

兼容版omitBy的完整实现位于 src/compat/object/omitBy.ts,核心逻辑只有十几行:

export function omitBy<T, S extends T>( object: Record<string, T> | Record<number, T> | object | null | undefined, shouldOmit?: ValueKeyIteratee<T[keyof T]> | ValueKeyIteratee<T> ): Record<string, S> | Record<number, S> | Partial<T> { if (object == null) { return {}; } const result: Partial<T> = {}; const predicate = createIteratee(shouldOmit ?? identity); const keys = [...keysIn(object), ...getSymbolsIn(object)] as Array<keyof T>; for (let i = 0; i < keys.length; i++) { const key = (isSymbol(keys[i]) ? keys[i] : keys[i].toString()) as keyof T; const value = object[key as keyof typeof object]; if (!predicate(value, key, object)) { result[key] = value; } } return result; }

关键点有三:

6.1 键的收集:keysIn + getSymbolsIn

与简单实现只用Object.keys不同,兼容版拼接了两个来源:

  • keysIn(object)(src/compat/object/keysIn.ts):返回自身 + 原型链上所有可枚举字符串键(含继承属性)。它对非对象值会先装箱(Object(object)),对数组/类数组对象按索引展开,对原型对象会剔除constructor
  • getSymbolsIn(object)(src/compat/_internal/getSymbolsIn.ts):沿原型链逐层收集所有 Symbol 键while (object) { ...; object = Object.getPrototypeOf(object); })。

正因为如此,兼容版omitBy能够处理继承属性Symbol 键属性,这是它与现代版最本质的实现差异(现代版只用Object.keys,见 src/object/omitBy.ts)。

6.2 键的归一化与 -0 符号保留

遍历时,非 Symbol 键统一调用.toString()转为字符串,Symbol 键保持原样(isSymbol判定来自 src/compat/predicate/isSymbol.ts)。测试用例 omitBy.spec.ts 验证了一个微妙行为:-00作为键时符号会被保留——{ '-0': 'a', 0: 'b' }经过omitBy过滤后,-0键与0键不会互相串扰。

6.3 保留条件:!predicate(...)

循环体内只有一行过滤逻辑:if (!predicate(value, key, object)) { result[key] = value; }。谓词返回假值时属性被保留,返回真值时被剔除。注意谓词以(value, key, object)三个实参被调用——第三个参数object正是源对象,这解释了 3.6 节示例中能在谓词内访问obj的原因。

七、测试用例揭示的兼容语义

src/compat/object/omitBy.spec.ts 是理解兼容版行为边界的最好教材,除前文已述的用例(null 源对象、null 谓词、-0/0符号)外,还覆盖了:

  • 继承属性被纳入过滤(L28-L35):Foo.prototype = object后,new Foo()的实例属性连同原型上的属性一起参与谓词过滤,结果与直接过滤源对象一致;
  • Symbol 键参与过滤且不可枚举 Symbol 不进入结果(L47-L109):自身 Symbol、原型链上的可枚举 Symbol 都会被遍历并参与谓词判断;而通过Object.defineProperty定义为enumerable: false的 Symbol 不会出现在结果中(symbol3 in actualfalse);
  • 数组对象按索引过滤(L111-L114):omitBy([1, 2, 3], ...)返回{ 1: 2 },与文档 3.5 节的示例行为一致;
  • 带数字length属性的普通对象不会被误判为类数组(L129-L146):{ level: 'error', message: 'hi', length: 104, empty: undefined }配合isNil过滤时,length: 104作为普通属性被保留,同时 Symbol 键属性也完好保留。

这些行为共同保证了与 Lodash 的omitBy语义高度一致,这正是es-toolkit/compat入口存在的意义。

八、性能提示:为什么文档推荐现代版

原文档开头给出了明确的警告:请优先使用 es-toolkit 的现代版omitBy(docs/reference/object/omitBy.md),理由是兼容版相对较慢,慢的来源正是本文分析的三个环节:

  1. 类数组对象的检查keysIn内部需要先判断isArrayLikeisPrototypeisBufferisTypedArray等(src/compat/object/keysIn.ts),存在额外分支开销;
  2. iteratee 转换:谓词要经过createIteratee的简写归一化(函数/属性名/二元组/部分对象的分派),比直接调用回调函数多一层间接;
  3. 键的转换过程:遍历时需要对每个 key 执行isSymbol判断与.toString()转换,还要额外收集原型链与 Symbol 键。

而现代版实现(src/object/omitBy.ts)只做最朴素的事:Object.keys(obj)取自身可枚举字符串键 → 循环调用shouldOmit(value, key)→ 收集结果,无继承属性、无 Symbol、无 iteratee 简写、无类型转换。如果不需要 Lodash 兼容语义(不依赖继承属性、Symbol 键、-0符号或简写谓词),应使用es-toolkit/object的版本以获得更好的性能;只有迁移 Lodash 代码、需要行为完全一致时才使用es-toolkit/compat

九、快速选型总结

维度es-toolkit/compat的 omitByes-toolkit/object的 omitBy
导入入口import { omitBy } from 'es-toolkit/compat'import { omitBy } from 'es-toolkit/object'
遍历范围自身 + 原型链可枚举键 + Symbol 键仅自身可枚举字符串键
谓词形态函数 / 属性名 /[key, value]/ 部分对象(iteratee 简写)仅函数
谓词参数(value, key, object)(value, key)
null/undefined返回{}——(类型上要求对象)
默认谓词identity必须显式传入
性能相对较慢(类数组检查 + iteratee 转换 + 键转换)更快(最小化实现)
适用场景从 Lodash 迁移、需要完全兼容语义新项目、追求性能

总之:omitBy是"反向过滤"的对象工具,谓词返回true即剔除。默认场景推荐现代版;需要 Lodash 级兼容行为时,理解keysIn+getSymbolsIn+createIteratee这套兼容管线,你就能准确预测它在继承属性、Symbol、-0length属性等边界输入下的行为。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询