Lodash 源码阅读-baseMatchesProperty
概述
baseMatchesProperty
是 Lodash 内部的工具函数,用来创建一个检查函数,这个函数会判断对象指定路径上的值是否与给定的源值相等。它是 _.matchesProperty
方法的核心实现,常用于集合操作中创建属性匹配断言函数。
前置学习
依赖函数
isKey
:检查值是否为简单属性路径isStrictComparable
:检查值是否可以使用严格相等(===
)比较matchesStrictComparable
:创建使用严格相等比较的匹配函数toKey
:将值转换为属性路径的键get
:获取对象指定路径上的值hasIn
:检查路径是否存在于对象中baseIsEqual
:深度比较两个值是否相等
技术知识
- 高阶函数:返回函数的函数
- 属性路径:表示嵌套对象属性的访问路径
- 闭包:函数记住并访问其词法作用域
- 位掩码:使用位运算符组合多个标志
- 性能优化:通过快速路径提高函数执行效率
源码实现
javascript
function baseMatchesProperty(path, srcValue) {
if (isKey(path) && isStrictComparable(srcValue)) {
return matchesStrictComparable(toKey(path), srcValue);
}
return function (object) {
var objValue = get(object, path);
return objValue === undefined && objValue === srcValue
? hasIn(object, path)
: baseIsEqual(
srcValue,
objValue,
COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG
);
};
}
实现思路
baseMatchesProperty
根据路径和源值的特性,采用不同策略创建匹配函数:
-
当路径是简单属性键且源值可以使用严格相等比较时,采用简单高效的
matchesStrictComparable
函数 -
否则,创建一个更复杂的匹配函数,这个函数会:
- 获取对象指定路径上的值
- 处理特殊情况(如 undefined 值)
- 使用深度比较判断属性值是否匹配
这种分层设计既保证了比较的准确性,又通过快速路径优化了简单场景的性能。
源码解析
快速路径优化
javascript
if (isKey(path) && isStrictComparable(srcValue)) {
return matchesStrictComparable(toKey(path), srcValue);
}
这段代码是性能优化,当满足两个条件时使用简单高效的比较方式:
-
path
是简单属性键(通过isKey
判断)简单属性键是像
'name'
这样的直接属性,而不是'user.profile.name'
或['user', 'profile', 'name']
这样的嵌套路径 -
srcValue
可以使用严格相等比较(通过isStrictComparable
判断)可严格比较的值包括基本类型(字符串、数字、布尔值等)和非对象值
以下是 isStrictComparable
的实现:
javascript
function isStrictComparable(value) {
return value === value && !isObject(value);
}
这个函数检查值是否可以使用 ===
操作符安全比较,它排除了 NaN(因为 NaN !== NaN
)和对象(需要深度比较)。
当条件满足时,matchesStrictComparable
会创建一个简单函数:
javascript
function matchesStrictComparable(key, srcValue) {
return function (object) {
if (object == null) {
return false;
}
return (
object[key] === srcValue &&
(srcValue !== undefined || key in Object(object))
);
};
}
这个函数直接使用 ===
比较,避免了更复杂的深度比较操作。
创建复杂匹配函数
javascript
return function (object) {
var objValue = get(object, path);
return objValue === undefined && objValue === srcValue
? hasIn(object, path)
: baseIsEqual(
srcValue,
objValue,
COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG
);
};
当不满足快速路径条件时,函数返回一个更复杂的匹配函数:
-
使用
get(object, path)
获取对象指定路径上的值javascriptfunction get(object, path, defaultValue) { var result = object == null ? undefined : baseGet(object, path); return result === undefined ? defaultValue : result; }
这支持深层嵌套路径访问,如
'user.profile.name'
或['user', 'profile', 'name']
-
处理特殊情况:对象值和源值都是
undefined
javascriptobjValue === undefined && objValue === srcValue;
这种情况下,需要区分"路径不存在"和"路径存在但值为 undefined"
通过
hasIn(object, path)
检查路径是否存在于对象中:javascriptfunction hasIn(object, path) { return object != null && hasPath(object, path, baseHasIn); }
-
对于其他情况,使用
baseIsEqual
进行深度比较javascriptbaseIsEqual( srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG );
使用的位掩码标志:
COMPARE_PARTIAL_FLAG
(值为 1):启用部分比较模式COMPARE_UNORDERED_FLAG
(值为 2):启用无序比较模式
这允许比较复杂对象和数组,即使它们的属性顺序不同
实际案例:
javascript
// 创建一个检查函数,判断对象的 'user.type' 是否为 'admin'
const isAdmin = baseMatchesProperty("user.type", "admin");
// 使用该函数检查对象
isAdmin({ user: { type: "admin", name: "John" } }); // 返回 true
isAdmin({ user: { type: "user", name: "Jane" } }); // 返回 false
对于数组路径和复杂对象比较:
javascript
// 检查对象的 user.hobbies 数组是否包含 ['reading', 'coding']
const hasHobbies = baseMatchesProperty(
["user", "hobbies"],
["reading", "coding"]
);
// 由于使用 COMPARE_UNORDERED_FLAG,数组元素顺序不影响结果
hasHobbies({ user: { hobbies: ["coding", "reading"] } }); // 返回 true
总结
baseMatchesProperty
是 Lodash 中属性值匹配功能的核心实现,它有几个显著特点:
-
分层设计:根据输入参数特性选择不同实现策略,简单情况用快速方法,复杂情况用完整比较
-
性能优化:通过快速路径处理常见简单情况,减少不必要的复杂比较
-
完备性:处理了各种边缘情况,如 undefined 值和不存在的路径
-
灵活性:支持简单属性和复杂嵌套路径,同时支持基本值比较和深度对象比较
baseMatchesProperty
的典型应用场景包括:
- 在集合操作(如
_.find
、_.filter
)中作为断言函数 - 实现数据查询和过滤条件
- 创建基于属性值的数据验证器
- 在函数式编程中组合构建复杂条件
这种实现方式体现了函数式编程的强大之处:高阶函数和闭包的结合使我们可以动态生成专用的比较函数,既保证了灵活性又维持了高性能。