es-toolkit negate 函数完全指南:一行代码反转布尔谓词,重塑过滤与条件逻辑
【免费下载链接】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
导读
negate是 es-toolkit 在 Function 模块 中提供的高阶函数工具,它接收一个返回布尔值的函数(谓词),并返回一个逻辑结果完全相反的新函数。在数组过滤、条件判断、断言组合等场景中,negate能让你用「正向思维」定义规则,再通过取反复用同一份逻辑,避免重复书写!表达式或冗余的反向谓词。读完本文,你将掌握negate的 API 签名、源码级实现原理、与Array.prototype.filter的搭配技巧,以及它与 lodash 兼容版本negate的行为差异。
一、negate是什么:反转布尔结果的函数工厂
negate是典型的「高阶函数」(higher-order function):它不直接参与业务计算,而是接收一个函数、返回一个新函数。其核心职责在官方文档(英文版、日文版)中被定义为:
创建一个新函数,将「返回 true 或 false 的函数」的返回值反转。
用一个最简单的类型签名即可概括其行为:
const negatedFunc = negate(booleanFunc);booleanFunc接收任意参数并返回布尔值;negatedFunc与booleanFunc接收完全相同的参数,但返回值永远是!booleanFunc(...)的结果。
适用场景
negate最适合用于反转条件函数或过滤逻辑。例如,你已经写好了一个「判断偶数」的函数,想得到「判断奇数」的函数,无需重写一遍n % 2 !== 0,直接negate(isEven)即可。这种「定义一次、双向复用」的思路在条件组合密集的代码中能显著减少重复与出错概率。
二、快速上手:基本用法与数组过滤实战
从 negate 官方文档 的示例出发,negate的标准导入方式与基本用法如下:
import { negate } from 'es-toolkit/function'; // 基本的な使用法(基本用法) const isEven = (n: number) => n % 2 === 0; const isOdd = negate(isEven); console.log(isEven(2)); // true console.log(isOdd(2)); // false console.log(isEven(3)); // false console.log(isOdd(3)); // true // 配列フィルタリングで使用(用于数组过滤) const numbers = [1, 2, 3, 4, 5, 6]; const evenNumbers = numbers.filter(isEven); console.log(evenNumbers); // [2, 4, 6] const oddNumbers = numbers.filter(negate(isEven)); console.log(oddNumbers); // [1, 3, 5]上面的示例揭示了negate最典型的实战组合:filter与negate天然互补。filter保留谓词返回true的元素,negate负责把谓词反转,于是filter(negate(pred))就等价于「过滤掉满足pred的元素」。
文档还给出了对复杂条件函数取反的示例——反转字符串长度判断:
import { negate } from 'es-toolkit/function'; const isLongString = (str: string) => str.length > 5; const isShortString = negate(isLongString); const words = ['hi', 'hello', 'world', 'javascript']; const longWords = words.filter(isLongString); console.log(longWords); // ['javascript'] const shortWords = words.filter(isShortString); console.log(shortWords); // ['hi', 'hello', 'world']注意isLongString与isShortString接收的参数(一个字符串)和返回类型(布尔值)完全一致,这正是negate类型保真(type-preserving)的体现:入参签名不变,只翻转返回值。
三、API 参考:参数与返回值
negate(func)
| 项目 | 说明 |
|---|---|
参数func(类型F) | 一个返回布尔值的函数(谓词函数),F被约束为(...args: any[]) => boolean |
返回值(类型F) | 一个新函数:与原函数接收相同参数,但返回相反的布尔值 |
关键点有二:
- 参数透传:返回的新函数与
func拥有完全一致的参数列表,调用时原样透传,因此negate不会破坏原有函数的调用约定; - 类型保留:从源码可见
negate的返回类型直接复用泛型F,而不是简单地返回(...args: any[]) => boolean。这意味着在 TypeScript 中,const isOdd: (n: number) => boolean = negate(isEven)的类型推断是精确的,不会退化成宽泛的函数类型。
四、源码解析:三行核心实现与 lodash 兼容版的差异
4.1 核心实现(es-toolkit 原生版)
negate的核心实现位于 src/function/negate.ts,全文仅有一个导出函数:
export function negate<F extends (...args: any[]) => boolean>(func: F): F { return ((...args: any[]) => !func(...args)) as F; }从源码结构可以提炼出三条实现事实:
- 闭包捕获:返回的箭头函数通过闭包持有
func,并在每次调用时执行!func(...args),用逻辑非(!)对结果取反; this绑定不传递:原生版返回的是箭头函数,箭头函数不绑定自己的this,因此调用negatedFunc时外层的this不会被传入func。对于纯函数式谓词(如isEven)这毫无影响,但如果你的谓词依赖this,请留意这一点;- 无参数校验:原生版没有对
func做类型检查,直接调用!func(...args)。若传入非函数,会在运行时抛出TypeError,类型层面则由泛型约束F extends (...args: any[]) => boolean在编译期拦截。
对应的单元测试位于 src/function/negate.spec.ts,使用 vitest 验证了三个关键行为:
negate的返回值是一个函数(typeof negate(() => true) === 'function');- 布尔结果被正确反转(
negate(() => true)()为false,negate(() => false)()为true); - 与
filter组合后能正确筛出奇数([1, 2, 3, 4, 5, 6].filter(negate(isEven))等于[1, 3, 5])。
4.2 lodash 兼容版(compat 模块)
es-toolkit 还提供与 lodash 行为对齐的兼容版本,实现在 src/compat/function/negate.ts。对比两者,兼容版存在三处显著差异:
- 显式校验:兼容版在调用前检查
typeof func !== 'function',不满足时抛出TypeError('Expected a function'),与 lodash 的报错行为一致; this绑定传递:兼容版返回普通函数(function (this: any, ...args: any[])),并通过func.apply(this, args)把调用时的this透传给原函数,行为更贴近 lodash 语义;- 双重重载签名:兼容版同时导出了基于元组类型
T extends any[]的签名与基于函数类型F的签名,为不同调用场景提供更灵活的类型推导。
如果项目从 lodash 迁移且依赖其this传递语义,应优先选用 compat 入口(见下文「如何引用」);如果追求极致的包体积与简单性,原生版是更优选择。
五、组合与边界:negate的进阶用法
5.1 与filter、some、every等方法的组合
negate不仅适用于数组过滤,凡是以谓词为参数的 API 都能与其组合:
import { negate } from 'es-toolkit/function'; const isEven = (n: number) => n % 2 === 0; const numbers = [1, 2, 3, 4, 5, 6]; // 是否存在奇数 console.log(numbers.some(negate(isEven))); // true // 是否全部为奇数 console.log(numbers.every(negate(isEven))); // false // 找到第一个奇数 console.log(numbers.find(negate(isEven))); // 1 // 第一个奇数的下标 console.log(numbers.findIndex(negate(isEven))); // 0由于negate返回的函数与原函数签名一致,这些内建数组方法的回调位置都能直接接受negate(...)的产物,无需额外包装。
5.2 链式取反与语义可读性
negate(negate(fn))在逻辑上等价于fn本身,因此一般不需要双重取反。更常见的需求是先组合、后取反,例如把多个条件用&&组合成谓词后再整体取反,negate依然适用,因为它只关心入参函数的布尔返回值,不关心谓词内部逻辑有多复杂。
5.3 注意事项
negate期望入参返回「可被!归一化为布尔值」的值;虽然 JavaScript 的!对任意值都成立,但把非布尔返回值(如0、''、null)传入negate后,取反结果会基于真值(truthiness)语义,而不是字面意义上的「相反值」;- 原生版不传递
this,若谓词内部使用了this(例如对象方法),建议改用 compat 版或先用bind固定上下文; negate每次调用都会创建新的闭包,在热路径中反复创建会带来微量开销,但现代 JS 引擎对此优化良好,常规业务场景无需顾虑。
六、如何引用:从安装到按需导入
negate随 es-toolkit 的 Function 模块一起发布,可以通过两种路径导入:
// 方式一:按模块导入(推荐,利于 tree shaking) import { negate } from 'es-toolkit/function'; // 方式二:从主入口导入(整个库统一入口) import { negate } from 'es-toolkit';从 src/index.ts 可以看到,negate经由export * from './function/index.ts'被聚合到主入口;而 src/function/index.ts 则显式export { negate } from './negate.ts'作为模块内导出。因此两种写法均有效,按需导入可让打包器只保留实际用到的代码,契合 es-toolkit 主打的小体积与 tree shaking 特性。
lodash 兼容行为则从 compat 入口引用:
import { negate } from 'es-toolkit/compat';安装方式与仓库其他功能一致,通过npm install es-toolkit安装后即可使用,完整文档可参考 docs/reference/function/negate.md 及各语言版本(日文版、中文版)。
七、总结
negate是一个「小而美」的高阶函数:核心实现仅一行逻辑非运算,却能在过滤、断言、条件组合等场景中消除大量重复代码。回顾本文要点:
- 行为:接收布尔谓词,返回参数签名相同、布尔结果相反的新函数;
- 典型组合:
filter(negate(pred))即「排除满足 pred 的元素」,some/every/find同理; - 实现差异:原生版(src/function/negate.ts)极简且不传
this;compat 版(src/compat/function/negate.ts)带参数校验、透传this并贴近 lodash 语义; - 可靠性:行为由 src/function/negate.spec.ts 的单元测试覆盖验证。
下次当你写出!somePredicate(x)或被迫定义两个语义相反的谓词时,先想想negate——一行取反,逻辑更清晰。
【免费下载链接】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),仅供参考