es-toolkit 兼容 lodash 的 isMatch 详解对象部分匹配的原理、边界与实战【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkitisMatch是 es-toolkit 的es-toolkit/compat兼容层中提供的谓词函数用于判断目标对象是否部分匹配源对象的形状与值只要源对象source中的每个属性都能在目标对象target中找到且值相等就返回true两者无需完全一致。本文以 isMatch.md 为骨架结合 isMatch.ts、isMatchWith.ts 的源码实现与 isMatch.spec.ts 的测试用例完整讲解其用法、类型匹配规则、边界行为、性能考量以及更快的替代方案帮助你写出既正确又高效的匹配逻辑。基本用法与核心概念isMatch的签名非常直观const result isMatch(target, source);target(unknown)被检查的对象即被匹配方source(unknown)充当匹配模板的对象即匹配模式返回值 (boolean)若target部分匹配source的形状与值则返回true否则false。核心语义是source的所有属性都必须存在于target中且值相同但target可以拥有source中没有的额外属性。这也是部分匹配partial match的含义——它不等价于isEqual的完全相等判断。import { isMatch } from es-toolkit/compat; // 对象部分匹配a、b 匹配c 是 target 的额外属性不影响结果 isMatch({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 }); // true // source 中的 c 在 target 中不存在匹配失败 isMatch({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 }); // false // 嵌套对象深层属性同样支持部分匹配 isMatch({ user: { name: Alice, age: 25, city: Seoul } }, { user: { name: Alice, age: 25 } }); // true注意isMatch是从es-toolkit/compat子路径导入的在 compat.ts 中统一导出这正是为 lodash 用户提供的兼容入口。支持的数据类型与匹配规则从源码看isMatch本身只是一个薄封装直接把比较逻辑委托给isMatchWithexport function isMatch(target: object, source: object): boolean { return isMatchWith(target, source, () undefined); }传入的 customizer 永远返回undefined意味着所有属性都走默认比较逻辑见 isMatch.ts。真正的类型分发逻辑在isMatchWithInternal中按typeof source与具体类型分别处理见 isMatchWith.ts对象Object逐一遍历source的键要求key in target为真且对应值匹配数组Arraysource的每个元素都能在target中找到匹配项顺序无关且每个target元素最多被使用一次处理重复值Mapsource中的每个键值对都存在于target中且值匹配Setsource的每个元素都能在target中找到函数Function若函数自身带属性则按对象规则比较否则按引用严格相等eq比较原始值Primitive使用严格相等比较。数组顺序无关的部分匹配数组匹配是isMatch最实用也最容易踩坑的特性之一。在isArrayMatch的实现中见 isMatchWith.ts外层遍历source元素内层在target中寻找未使用过的匹配项并用countedIndex集合保证同一目标元素不会重复匹配// 数组部分匹配顺序无关 isMatch([1, 2, 3, 4], [2, 4]); // true2 和 4 都在数组中 isMatch([1, 2, 3], [1, 2, 3]); // true完全匹配 isMatch([1, 2], [1, 2, 3]); // false3 不在 target 中 // 重复值必须按数量匹配而不是只判断存在性 const objects [{ a: [1, 2] }, { a: [2, 2] }]; objects.filter(x isMatch(x, { a: [2, 2] })); // 只有 [{ a: [2, 2] }] 通过上述重复值用例直接对应测试中的 should partial match arrays with duplicate values见 isMatch.spec.ts[1, 2]只有一个 2无法满足[2, 2]的匹配模式。同理测试 should partial match arrays 还验证了{ a: [b, d] }无法匹配{ a: [c, d] }因为b不存在见 isMatch.spec.ts。数组还可以嵌套对象模式进行部分匹配const objects [ { a: [{ b: 1, c: 2 }, { b: 4, c: 5, d: 6 }] }, { a: [{ b: 1, c: 2 }, { b: 4, c: 6, d: 7 }] }, ]; objects.filter(x isMatch(x, { a: [{ b: 1 }, { b: 4, c: 5 }] })); // 只有第一个对象通过见 isMatch.spec.ts should partial match arrays of objectsMap 与 Set键值对级别的部分匹配Map 匹配要求source中的每个键都存在于target且对应值匹配见 isMatchWith.tsconst targetMap new Map([ [a, 1], [b, 2], [c, 3], ]); const sourceMap new Map([ [a, 1], [b, 2], ]); isMatch(targetMap, sourceMap); // true测试 should partial match maps 展示了三种情形的对照source只有b时仅匹配含b的对象source为空 Map 时匹配所有对象source含不存在的键c时全部失败见 isMatch.spec.ts。Set 的匹配会先把两者展开为数组再复用数组匹配逻辑见 isMatchWith.tsconst targetSet new Set([1, 2, 3, 4]); const sourceSet new Set([2, 4]); isMatch(targetSet, sourceSet); // true函数与原始值函数按引用比较isMatch({ a: noop }, { a: noop })为true而isMatch({ a: noop }, { a: () {} })为false见 isMatch.spec.ts函数也可作为 source 携带属性source是函数但带自有属性时会按对象规则比较其属性见 isMatchWith.ts-0与0视为相等isMatch({ a: 0 }, { a: -0 })与反向均为true见 isMatch.spec.ts。边界行为空值、空对象与 undefinedisMatch的边界行为在 lodash 兼容场景下非常重要下面这些规则均能在源码与测试中找到依据空 source 恒为真当source为空对象、空数组、空 Map、空 Set 时无论target是什么包括null/undefined都返回trueisMatch({ a: 1 }, {}); // true isMatch([1, 2, 3], []); // true isMatch(null, {}); // true测试 should return true when comparing an empty source to a nullish object对应源码isArrayMatch/isMapMatch/isSetMatch开头都有source.length 0/source.size 0直接返回true的短路分支见 isMatchWith.ts。顶层空对象模式匹配任意值isMatch(bar, {})为true甚至可以匹配原始值bar的length和下标属性isMatch(bar, { length: 3 }); // true isMatch(bar, { 0: b }); // true isMatch(bar, { length: 4 }); // false测试 should match object patterns against primitive targets at the top level 完整覆盖了这些场景见 isMatch.spec.ts。底层原因在于isMatchWithInternal的default分支当source是原始值而target是对象时若处于根层级isRoot除空字符串外一律返回true见 isMatchWith.ts。嵌套空对象模式只匹配对象与顶层不同嵌套层级中{ value: {} }不会匹配字符串、数字、布尔、null、undefined、数组甚至Date只会匹配普通对象。isObjectMatch中通过getTag(target)判断 tag 必须为[object Object]或[object Arguments]见 isMatchWith.ts对应测试 should not match nested empty object patterns against non-object targets见 isMatch.spec.tsisMatch({ value: bar }, { value: {} }); // false isMatch({ value: [1, 2, 3] }, { value: {} }); // false isMatch({ value: { b: 1 } }, { value: {} }); // trueundefined与null的显式匹配source中的undefined/null必须与target中的对应值同为undefined/null才算匹配——属性存在但值不同会直接判负isMatch({ a: 1 }, { b: undefined }); // falseb 根本不存在 isMatch({ a: 1, b: undefined }, { b: undefined }); // true isMatch({ a: 1 }, { a: null }); // false // 嵌套同样适用 isMatch({ a: { b: 2, c: undefined } }, { a: { c: undefined } }); // true对应isObjectMatch中的两个显式分支source[key] undefined target[key] ! undefined与source[key] null target[key] ! null都直接返回false见 isMatchWith.ts。继承属性target 的继承属性会被纳入匹配key in target判断包含原型链而 source 的继承属性不会被用于匹配只遍历Object.keys自有键。测试 should match inherited string keyed object properties 与 should not match by inherited source properties 分别验证了这两个方向见 isMatch.spec.ts。更快的替代方案与性能基准isMatch的优势在于表达力与兼容性但如果你只需要简单场景文档给出了三个更直接、更快的替代方案// 1. 完全相等检查更快 import { isEqual } from es-toolkit; isEqual(obj1, obj2); // 2. 特定属性检查更清晰 target.a source.a target.b source.b; // 3. 对象结构检查 Object.keys(source).every(key target[key] source[key]);在仓库的 isMatch.bench.ts 中es-toolkit 的isMatch与 lodash 的isMatch使用同一份深层嵌套对象样本{ a: { b: { c: 1, d: 2 }, e: 3 }, f: 4 }匹配{ a: { b: { c: 1 } } }进行基准对比。你可以通过以下方式在本地复现基准# 在仓库根目录运行性能基准依赖 vitest 的 bench 能力 yarn vitest run benchmarks/performance/isMatch.bench.ts --bench需要说明的是基准脚本只定义了对比样本具体耗时数据需在本机运行后查看选择isMatch还是手写every判断取决于你更看重兼容性、可读性还是极致性能。从 isMatch 到 isMatchWith自定义比较如果默认的严格比较满足不了需求可以使用isMatchWith(target, source, customizer)文档见 isMatchWith.md。customizer 的签名是(objValue, srcValue, key, object, source, stack)返回true表示匹配、false表示不匹配、undefined则回落到默认逻辑继续递归见 isMatchWith.tsimport { isMatchWith } from es-toolkit/compat; // 大小写不敏感的字符串比较 const caseInsensitiveCompare (objVal, srcVal) { if (typeof objVal string typeof srcVal string) { return objVal.toLowerCase() srcVal.toLowerCase(); } return undefined; // 其余走默认行为 }; isMatchWith({ name: ALICE, age: 25 }, { name: alice }, caseInsensitiveCompare); // true // 数值范围比较 const rangeCompare (objVal, srcVal, key) { if (key age typeof srcVal object srcVal.min ! undefined) { return objVal srcVal.min objVal srcVal.max; } return undefined; }; isMatchWith({ name: John, age: 25 }, { age: { min: 18, max: 30 } }, rangeCompare); // true // 数组长度比较 const lengthCompare (objVal, srcVal, key) { if (key items Array.isArray(objVal) typeof srcVal number) { return objVal.length srcVal; } return undefined; }; isMatchWith({ items: [a, b, c], count: 3 }, { items: 3 }, lengthCompare); // true从源码看isMatch正是isMatchWith传入恒返回undefined的 customizer 的特例因此两者共享全部匹配逻辑而isMatchWithInternal中的stack参数用于环引用检测避免循环引用导致无限递归见 isMatchWith.ts。典型应用场景过滤器、守卫与匹配器isMatch最常见的实战场景是与filter/some等数组方法配合把对象模式用作筛选条件import { isMatch } from es-toolkit/compat; const users [ { id: 1, name: Alice, role: admin, active: true }, { id: 2, name: Bob, role: user, active: false }, { id: 3, name: Carol, role: admin, active: false }, ]; // 筛选所有 role 为 admin 的用户target 的额外属性不影响匹配 users.filter(user isMatch(user, { role: admin })); // [{ id: 1, ... }, { id: 3, ... }] // 多条件组合 嵌套结构 users.filter(user isMatch(user, { role: admin, active: true })); // [{ id: 1, ... }] // 数组字段的部分匹配顺序无关 const products [ { name: A, tags: [sale, new] }, { name: B, tags: [new] }, { name: C, tags: [sale] }, ]; products.filter(p isMatch(p, { tags: [new] })); // 命中 A、B仓库中的 matches.ts 进一步展示了isMatch的函数式封装matches(source)会先cloneDeep模板再返回一个可复用的匹配器函数matchesProperty也依赖isMatch实现。这意味着你可以把isMatch当作构建高阶匹配器的基础原语用于实现按模式过滤的工具函数或路由守卫。总结es-toolkit/compat的isMatch以极小的 API 面覆盖了对象、数组、Map、Set、函数与原始值的深层部分匹配其核心规则可概括为四句话source 的每个属性都必须存在于 target 且值匹配target 可以有额外属性数组按元素匹配且顺序无关重复值按数量计数空 source空对象/数组/Map/Set恒匹配但嵌套的空对象模式只匹配普通对象undefined/null需要严格对位继承属性只在 target 侧参与匹配。需要自定义比较时升级到isMatchWith需要完全相等时改用isEqual需要极致性能的简单场景则直接手写every判断。相关源码、测试与基准分别位于 isMatch.ts、isMatchWith.ts、isMatch.spec.ts 与 isMatch.bench.ts可随时深入阅读。【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考