es-toolkit/compat `overEvery` 源码级解析:多条件复合校验函数的实现与使用
2026/9/16 11:01:17 网站建设 项目流程

es-toolkit/compatoverEvery源码级解析:多条件复合校验函数的实现与使用

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

导读

overEvery是 es-toolkit 兼容层(es-toolkit/compat)中用于「多条件复合校验」的工具函数:它接收多个谓词(predicate),返回一个新函数,只有当下游传入的值同时满足全部条件时才返回true。本文以 overEvery 官方文档 为主线,结合 overEvery 源码、iteratee 源码 与 overEvery 测试用例,讲解它的四种简写形式(函数、属性名、对象、属性-值对)、短路求值机制、类型重载以及它与原生Array.every的取舍,帮助你在从 Lodash 迁移或日常数据验证场景中正确、高效地使用它。

一、背景:overEvery在 compat 兼容层中的定位

es-toolkit 提供了两套 API:面向新项目的类型安全主库es-toolkit,以及 1:1 复刻 Lodash 行为的es-toolkit/compat兼容层。overEvery属于后者,其语义与 Lodash 的_.overEvery一致:创建一个函数,检查传入的值是否让所有谓词都返回真值(truthy)。

从源码结构看,它被同时导出在两个入口中:

  • compat 统一入口:export { overEvery } from './util/overEvery.ts';
  • browser 入口:export { overEvery } from './compat/util/overEvery.ts';

同时,与es-toolkit/compat的所有函数一样,它也支持按需单独导入(详见 compat 介绍文档),在无 tree-shaking 的环境(如 CommonJSrequire、React Native)下可以显著减小加载体积:

import { overEvery } from 'es-toolkit/compat'; // 或按需导入 import overEvery from 'es-toolkit/compat/overEvery';

二、核心 API 与基础用法

函数签名

const allValidator = overEvery(predicates);
  • 参数...predicatesArray<Function | string | object | Array>):要检查的谓词集合。可以是函数属性名(string)、对象(partial object)或属性-值对[key, value]数组)。
  • 返回值(...args: any[]) => boolean):返回一个新函数,当所有条件都满足时返回true,只要有一个条件不满足就返回false

overEvery与 Lodash 的_.overSome(任一条件满足即通过)是对偶关系,适合复合条件判断与数据校验场景。

用函数谓词做复合校验

import { overEvery } from 'es-toolkit/compat'; // 检查字符串条件 const isValidString = overEvery([ value => typeof value === 'string', value => value.length > 3, value => value.includes('o'), ]); isValidString('hello'); // => true isValidString('hi'); // => false (长度小于等于 3) isValidString('test'); // => false (不含 'o') // 检查数字范围 const isInRange = overEvery([ num => num >= 0, num => num <= 100, num => num % 1 === 0, // 检查是否为整数 ]); isInRange(50); // => true isInRange(-5); // => false (小于 0) isInRange(150); // => false (大于 100) isInRange(50.5); // => false (不是整数)

用简写谓词检查对象属性

除函数外,overEvery还支持三种 Lodash 风格的简写:

import { overEvery } from 'es-toolkit/compat'; // 检查对象属性 const isValidUser = overEvery([ 'name', // 属性名:name 属性是否为真值 { age: 30 }, // 部分对象:age 是否等于 30 ['active', true] // 属性-值对:active 是否为 true ]); isValidUser({ name: 'John', age: 30, active: true }); // => true isValidUser({ name: '', age: 30, active: true }); // => false (name 是空字符串) isValidUser({ name: 'John', age: 25, active: true }); // => false (age 不同)

三、源码实现:简写如何被统一为函数

overEvery之所以能同时接受函数、字符串、对象和数组四种谓词,关键在于它对每个谓词调用了 compat 层的iteratee转换器(见 iteratee.ts)。其转换规则为:

  1. 函数:原样返回,直接作为谓词使用;
  2. null/undefined:转换为恒等函数identity(即把传入值本身当作真值判断);
  3. 长度为 2 的数组(属性-值对):转换为matchesProperty谓词,检查对象在指定属性路径上的值是否与给定值匹配;
  4. 普通对象:转换为matches谓词,检查对象是否部分匹配;
  5. 其他(字符串、数字等 PropertyKey):转换为property谓词,取出对象上对应属性并判断其真值性。

matchesProperty的具体实现位于 matchesProperty.ts:它会通过get按属性路径取值,若取到undefined则回退用has判断属性是否存在,并对源值做cloneDeep拷贝以避免外部修改影响后续比较。这意味着简写形式并不只是「语法糖」,而是完整复刻了 Lodash 的深层匹配语义。

overEvery的主体实现(overEvery.ts)非常精简:

export function overEvery<T>( ...predicates: Array<((...values: T[]) => boolean) | ReadonlyArray<(...values: T[]) => boolean>> ): (...values: T[]) => boolean { return function (this: any, ...values: T[]) { for (let i = 0; i < predicates.length; ++i) { const predicate = predicates[i]; if (!Array.isArray(predicate)) { if (!createIteratee(predicate).apply(this, values)) { return false; } continue; } for (let j = 0; j < predicate.length; ++j) { if (!createIteratee(predicate[j]).apply(this, values)) { return false; } } } return true; }; }

从这段实现可以提炼出三个关键行为:

1. 短路求值(short-circuit)

循环中一旦某个谓词返回假值,函数立即return false,不再继续执行剩余谓词。overEvery 测试 用计数函数验证了这一点:overEvery(countTrue, countFalse, countTrue)调用后计数为 2,说明第三个countTrue从未被执行。这在谓词包含昂贵计算时能显著节省开销。

2. 数组谓词的扁平化(flattening)

overEvery(stubTrue, [stubFalse])这种「单个函数 + 数组」的混写形式是合法的:外层循环遇到数组时进入内层循环逐一展开执行。测试 overEvery.spec.ts 专门覆盖了这一行为。也正因如此,['active', true]这样长度为 2 的数组简写需要放在数组内部(如overEvery([['active', true]])),才能与「谓词列表」语义区分开。

3.this绑定透传

返回函数内部通过predicate.apply(this, values)调用谓词,把外层函数调用时的this透传给所有谓词。测试 overEvery.spec.ts 演示了这一点:将overEvery的结果挂到对象上作为方法调用时,谓词内的this指向该对象,从而可以访问this.athis.b。同时...values会把所有实参原样传递给每个谓词,测试 overEvery.spec.ts 确认over('a', 'b', 'c')时谓词收到完整的['a', 'b', 'c']参数列表。

四、类型系统:类型守卫与重载

overEvery的类型定义(overEvery.ts)提供了两个重载:

// 重载 1:两个谓词时启用类型守卫,返回值收窄为交集类型 export function overEvery<T, U extends T, V extends T>( predicate1: (value: T) => value is U, predicate2: (value: T) => value is V ): (value: T) => value is U & V; // 重载 2:通用形式 export function overEvery<T>( ...predicates: Array<((...values: T[]) => boolean) | ReadonlyArray<(...values: T[]) => boolean>> ): (...values: T[]) => boolean;

第一个重载是值得关注的类型细节:当恰好传入两个带类型守卫(type guard)的谓词时,返回函数本身也会被推断为类型守卫(value: T) => value is U & V。这意味着你可以把overEvery的结果直接当作类型守卫用在if分支或数组filter中,编译器会自动把变量收窄为U & V

type Pet = { name: string; legs: number }; const isDog = (p: Pet): p is Dog => p.legs === 4; const isNamed = (p: Pet): p is NamedDog => p.name.length > 0; const isValidPet = overEvery(isDog, isNamed); // 推断类型:(value: Pet) => value is Dog & NamedDog

五、测试验证与行为边界

overEvery.spec.ts 共覆盖 9 组行为,除了上文提到的短路求值、扁平化、参数透传、this绑定外,还包括:

  • nullish 谓词退化为 identityoverEvery(undefined, null)等价于直接判断传入值的真值性,over(true)trueover(false)false(见 测试 L32-L38);
  • property简写overEvery('b', 'a')检查对象上ba两个属性是否都为真值(L40-L46);
  • matches简写overEvery({ b: 2 }, { a: 1 })检查对象是否同时部分匹配两个条件(L48-L54);
  • matchesProperty简写与歧义区分overEvery(['a', 1])['a', 1]被当作「函数数组」展开后,'a'是属性简写、1是数字简写(取属性1的真值);而overEvery([['a', 1]])中内层二元数组才被识别为「属性-值对」(L56-L81)。这一测试清晰地划定了两种数组语义的边界。

六、性能提示与替代方案

原文档在开头给出了明确警告:overEvery在转换和检查谓词的过程中会产生额外开销,官方建议优先使用更快、更现代的Array.every

// 用原生 every 替代 overEvery const isValidString = (value: unknown) => [ (v: unknown) => typeof v === 'string', (v: string) => v.length > 3, (v: string) => v.includes('o'), ].every(fn => fn(value));

从实现看,开销来源有二:一是每个谓词(即使是纯函数)每次调用都要经过createIteratee转换判断(函数类型分支除外,见 iteratee.ts);二是返回函数通过apply动态调用并展开arguments。因此,overEvery的价值主要体现在与 Lodash 代码库的 1:1 兼容上——迁移阶段不改调用点即可工作(迁移流程详见 compat 介绍);而在可以自由改写的新代码中,直接用Array.every或手写&&组合条件通常是更优选择。

七、小结

  • overEvery(...predicates)创建「全条件通过」校验器,支持函数、属性名、部分对象、属性-值对四种谓词简写,由 iteratee.ts 统一转换;
  • 具备短路求值(遇假即停)、数组谓词扁平化this/多参数透传三个实现特性,均有测试用例佐证;
  • 恰好两个类型守卫谓词时,返回值被推断为value is U & V类型守卫,可直接用于类型收窄;
  • 作为 Lodash 兼容 API,它优先服务于存量代码迁移;新代码请按官方建议优先使用Array.every

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

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

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

立即咨询