es-toolkit 的 cond 函数:用函数式风格编排多条件分支的 Lodash 兼容实现
【免费下载链接】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
导读
cond是 es-toolkit 兼容层(es-toolkit/compat)提供的一个工具函数:它接收一组"条件-动作"函数对,返回一个新的复合函数,按顺序逐个求值条件,命中第一个为真的条件时执行其对应的动作函数并返回结果,若全部条件均为假则返回undefined。本文以 cond 官方文档 为主体,结合 cond 源码实现、单元测试 与 性能基准,讲清它的用法、类型签名、底层工作原理、与 Lodash 的兼容行为,以及官方文档明确提示的性能注意点,帮助你判断"何时该用、何时不该用"。
一、核心概念:什么是cond
cond(pairs)接收一个由[predicate, func]二元组组成的数组,返回一个新函数。调用该新函数时,它会把收到的参数依次交给每个predicate(条件谓词)求值,一旦某个谓词返回真值,就调用同组的func(动作函数),并直接返回其结果;如果所有谓词都为假,则返回undefined。
const conditionFunction = cond(pairs);在函数式风格中,cond相当于一个"可组合的 if-else 链":它把离散的条件分支封装成一个可传递、可复用的值,让调用方无需关心分支细节,只需调用conditionFunction(...args)。
注意:es-toolkit 的
cond只存在于es-toolkit/compat兼容入口中,使用前需从该路径导入:import { cond } from 'es-toolkit/compat';
二、基础用法:按顺序匹配条件
官方文档给出的第一个场景是"按数值区间返回不同标签"。cond从数组第一个二元组开始依次判断,命中即返回,因此条件的书写顺序直接决定优先级:
import { cond } from 'es-toolkit/compat'; // Basic usage const getValue = cond([ [x => x > 10, x => 'big'], [x => x > 5, x => 'medium'], [x => x > 0, x => 'small'], [() => true, () => 'zero or negative'], ]); console.log(getValue(15)); // "big" console.log(getValue(8)); // "medium" console.log(getValue(3)); // "small" console.log(getValue(-1)); // "zero or negative"这里最后一个[() => true, () => 'zero or negative']是一个"兜底分支":由于() => true恒为真,任何在前面分支中未命中的输入都会落入此处,等效于 else 子句。这种"前分支写具体条件、末位写恒真兜底"是使用cond的典型模式。
关键语义:只执行第一个命中的分支
cond与普通 if-else 链一样是"短路"的——一旦某个条件为真,后续分支不再求值,且只有第一个命中的动作函数会被执行。官方文档用下面这个例子验证这一点:
import { cond } from 'es-toolkit/compat'; const checkValue = cond([ [x => x > 10, x => 'greater than 10'], [x => x < 5, x => 'less than 5'], ]); console.log(checkValue(15)); // "greater than 10" console.log(checkValue(3)); // "less than 5" console.log(checkValue(7)); // undefined (no matching condition)当输入7时,两个条件都不满足,函数返回undefined。这一行为在 cond.spec.ts 中有专门测试:cond([[stubFalse, stubA]])传入任意对象都会得到undefined,因为stubFalse恒为假。
三、进阶用法:对象模式匹配
cond的谓词并不仅限于数值比较,它接受任意谓词函数,因此天然适合做对象字段的模式匹配——把"角色判断 + 字符串格式化"这类逻辑收敛进一张"规则表":
import { cond } from 'es-toolkit/compat'; const processUser = cond([ [user => user.role === 'admin', user => `Admin: ${user.name}`], [user => user.role === 'user', user => `User: ${user.name}`], [user => user.role === 'guest', user => `Guest: ${user.name}`], [() => true, () => 'Unknown role'], ]); console.log(processUser({ name: 'John', role: 'admin' })); // "Admin: John" console.log(processUser({ name: 'Jane', role: 'user' })); // "User: Jane"这种写法的价值在于:规则表本身是一个可配置、可动态构建的数据结构,你可以把pairs数组存起来、按需重组或由外部配置驱动,比硬编码的 if-else 更灵活;代价则是可读性与性能的取舍(详见第五节)。
四、类型签名与参数说明
根据 cond 源码,该函数提供两组重载签名:
// 无参调用形式(谓词与动作都不接收输入参数) export function cond<R>(pairs: Array<[truthy: () => boolean, falsey: () => R]>): () => R; // 接收一个输入值的形式(谓词与动作共享同一个输入) export function cond<T, R>(pairs: Array<[truthy: (val: T) => boolean, falsey: (val: T) => R]>): (val: T) => R;| 项目 | 说明 |
|---|---|
pairs | Array<[predicate, func]>,由"条件函数 + 动作函数"组成的二元组数组 |
predicate | 条件函数,接收调用参数,返回布尔值(真值即命中) |
func | 动作函数,命中对应条件后执行,其返回值作为整个调用的结果 |
| 返回值 | (...args: any[]) => unknown,一个新的复合函数;全部条件不命中时返回undefined |
值得注意的底层细节:虽然重载签名限制为"无参"或"单参",但实际实现(export function cond(pairs: any[][]): (...args: any[]) => unknown)会对任意数量的参数求值,并且谓词与动作函数会收到完全相同的参数。这一点由 cond.spec.ts 验证:调用resultFunc('a', 'b', 'c')时,谓词和动作函数都收到['a', 'b', 'c']。
参数校验与this绑定
实现中还有两个容易被忽略的行为,均有测试佐证:
- 动作函数必须为函数类型:构造时若
pair[1]不是函数(如false、true),会直接抛出TypeError('Expected a function')。见 cond.ts 与对应测试 cond.spec.ts。校验在"构造阶段"进行,而不是调用阶段。 this透传:生成的复合函数会保留调用方的this上下文,谓词与动作函数均通过predicate.apply(this, args)/func.apply(this, args)调用。测试 cond.spec.ts 展示了一个利用this取属性值的场景。
谓词简写:iteratee的自动转换
这是cond与 Lodash 兼容的关键点之一。源码中每个谓词都会经过iteratee(predicate)归一化处理(见 cond.ts),而 iteratee 实现 支持以下输入形式:
- 函数:原样返回,直接作为谓词;
- 属性名(如
'c'):转换为"取该属性"的函数,真值即命中; - 属性-值二元组(如
['b', 1]):转换为"属性值是否等于给定值"的判断(matchesProperty); - 部分对象(如
{ a: 1 }):转换为"对象是否匹配该部分对象"的判断(matches)。
因此你可以把文档示例中的函数谓词写成简写形式,测试 cond.spec.ts 验证了这种写法与函数写法行为完全一致:
const resultFunc = cond([ [{ a: 1 }, () => 'a'], // 对象简写 → matches({ a: 1 }) [['b', 1], () => 'b'], // 属性-值简写 → matchesProperty('b', 1) ['c', () => 'c'], // 属性名简写 → property('c') ]);五、性能注意:官方明确建议优先使用 if-else / switch
cond文档开头带有一段醒目的 warning,这是本文最需要认真对待的工程建议:
由于复杂的 iteratee 处理、数组变换和函数校验,
cond函数运行较慢。建议改用更快、更清晰的 if-else 或 switch 语句。
从源码看,这个警告并非空穴来风,性能开销主要来自三处(见 cond.ts):
- 构造阶段的开销:
pairs.map(...)对每一对执行isFunction校验并调用iteratee做谓词归一化,生成新的processedPairs数组; - 调用阶段的间接层:每次调用都要经
predicate.apply/func.apply动态分发,且谓词是经过包装的 iteratee,比直接写if (x > 10)多一层函数调用; - 数组遍历:条件匹配是线性扫描
for循环,最坏情况(无命中)需要遍历全部二元组。
仓库 benchmarks/performance/cond.bench.ts 中同时用es-toolkit/compat/cond与lodash/cond各跑 100ms 的基准用例(覆盖命中第一个、第二个、第三个分支及全部未命中四种输入),如果你关心与 Lodash 的量化差异,可以在本仓库运行性能基准自行对比。
因此,实践上的选择标准是:
- 热路径、性能敏感、分支逻辑稳定:直接用 if-else 或 switch,可读性和性能都更好;
- 规则表可配置、需要动态组装分支、或追求函数式组合:
cond是合理选择,但要意识到它带来的间接调用开销; - 兜底分支:习惯上用
() => true或stubTrue作为最后一个二元组,模拟 else 语义,避免返回意外的undefined。
六、兼容定位与测试保障
cond是 es-toolkit 的 Lodash 兼容实现,从 compat 入口 导出,与 lodash 的_.cond保持一致的语义:条件按顺序求值、只执行第一个命中分支、全不命中返回undefined、支持matches/matchesProperty/property等谓词简写、动作函数非函数时抛TypeError。上述行为在 cond.spec.ts 中共有 6 组用例覆盖(条件组合、参数透传、谓词简写、无命中返回 undefined、TypeError 校验、this 绑定),可作为迁移到 es-toolkit 时的行为对照清单。
总结
cond为函数式风格的多条件分支提供了一种紧凑、可组合、可配置的写法,支持函数谓词与 Lodash 风格的简写谓词,且严格遵循"顺序匹配、短路执行、全不命中返回 undefined"的语义。但在使用前请记住文档中的明确警告:它比 if-else / switch 更慢、间接层更多,适合规则表驱动的场景,而不适合性能敏感的冷热路径。理解 cond.ts 的实现细节(iteratee 归一化、构造期校验、this 透传)能帮助你在兼容性与性能之间做出正确的工程取舍。
【免费下载链接】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),仅供参考