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);- 参数
...predicates(Array<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)。其转换规则为:
- 函数:原样返回,直接作为谓词使用;
null/undefined:转换为恒等函数identity(即把传入值本身当作真值判断);- 长度为 2 的数组(属性-值对):转换为
matchesProperty谓词,检查对象在指定属性路径上的值是否与给定值匹配; - 普通对象:转换为
matches谓词,检查对象是否部分匹配; - 其他(字符串、数字等 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.a、this.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 谓词退化为 identity:
overEvery(undefined, null)等价于直接判断传入值的真值性,over(true)为true、over(false)为false(见 测试 L32-L38); property简写:overEvery('b', 'a')检查对象上b、a两个属性是否都为真值(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),仅供参考