Lodash 源码阅读-getMatchData
概述
getMatchData
是 Lodash 内部的一个工具函数,简单来说就是把对象的键值对变成特殊格式的数组。它把普通的对象 {a: 1, b: "hello"}
转换成 [["a", 1, true], ["b", "hello", true]]
这样的结构,主要是为了让 Lodash 内部的对象匹配功能(比如 _.isMatch
)能更高效地工作。
前置学习
依赖函数
keys
:获取对象自身的可枚举属性名数组,简单说就是拿到对象的所有键isStrictComparable
:检查值是否可以使用严格相等比较(===
),主要判断基本类型值和非 NaN 值
源码实现
javascript
function getMatchData(object) {
var result = keys(object),
length = result.length;
while (length--) {
var key = result[length],
value = object[key];
result[length] = [key, value, isStrictComparable(value)];
}
return result;
}
实现思路
getMatchData
的实现非常直接:先用 keys
函数拿到对象的所有键,然后从后往前遍历这些键(用 while (length--)
)。对于每个键,它都:
- 获取这个键对应的值
- 将数组中当前位置的元素(原本是键名)替换成一个新的三元素数组
- 这个三元素数组包含:[键名, 键值, 是否可用严格比较]
最后返回转换好的数组。整个过程就是把对象的结构重新组织,便于后续的匹配操作。
源码解析
初始化
javascript
var result = keys(object),
length = result.length;
首先获取对象的所有键,存在 result
变量中。比如对于对象 {a: 1, b: "hello"}
,result
就是 ["a", "b"]
。
keys
函数会根据对象类型选择合适的方法获取键:
- 对于普通对象,使用
Object.keys
或其兼容实现 - 对于数组类对象,会有特殊处理
转换键值对
javascript
while (length--) {
var key = result[length],
value = object[key];
result[length] = [key, value, isStrictComparable(value)];
}
这里从后往前遍历键数组,为什么要倒着遍历呢?这是一种常见的优化手段,倒序遍历通常比正序遍历要快一点。
对于每个键,函数都做了三件事:
- 获取当前键名
key
(比如 "a") - 获取这个键对应的值
value
(比如 1) - 用一个三元素数组
[key, value, isStrictComparable(value)]
替换原数组中的键名
最关键的是第三个元素:isStrictComparable(value)
的结果,这是一个布尔值,告诉我们这个值是否可以用严格相等(===
)来比较。
isStrictComparable 的作用
javascript
function isStrictComparable(value) {
return value === value && !isObject(value);
}
这个函数判断一个值是否满足两个条件:
value === value
:排除 NaN,因为NaN !== NaN
!isObject(value)
:值不是对象(基本类型值可以直接用===
比较)
基本类型值(数字、字符串、布尔值等)和非 NaN 值都会返回 true
,而对象、数组和 NaN 会返回 false
。
javascript
isStrictComparable(1); // true
isStrictComparable("string"); // true
isStrictComparable(true); // true
isStrictComparable({}); // false
isStrictComparable([]); // false
isStrictComparable(NaN); // false
为什么需要这个标识?
这个标识是 Lodash 匹配系统中的关键部分,它在 baseIsMatch
函数中被用于优化匹配性能。以下是它的实际用途:
-
优化匹配路径 :在
baseIsMatch
中,它会先检查这个标识。如果值是可以严格比较的(标识为true
),就直接用===
比较,这是最快的方式。javascript// baseIsMatch 中的代码片段 if ( noCustomizer && data[2] // data[2] 就是 isStrictComparable(value) 的结果 ? data[1] !== object[data[0]] : !(data[0] in object) ) { return false; }
-
避免不必要的深度比较 :对于简单值,不需要调用复杂的
baseIsEqual
函数进行深度比较,直接用===
就够了,这大大提高了性能。 -
处理不同类型的值:对于对象和数组,Lodash 会使用更复杂的比较方法;对于基本类型,则使用更简单快速的方法。
实际效果举例:
javascript
// 检查对象是否匹配模式
_.isMatch({ a: 1, b: { c: 3 } }, { a: 1 }); // true
// 内部执行过程(简化):
// 1. getMatchData({ a: 1 }) 返回 [["a", 1, true]]
// 2. baseIsMatch 看到第三个元素是 true,就直接用 === 比较 1 === 1
// 3. 比较成功,返回 true
返回结果
javascript
return result;
函数最后返回转换后的数组。例如,对于输入对象 { a: 1, b: 'string' }
,返回的结果是:
javascript
[
["a", 1, true],
["b", "string", true],
];
这种数据结构让 Lodash 的匹配算法能够:
- 快速获取键名和键值(不用再查询原对象)
- 根据第三个元素决定用什么比较策略(严格比较还是深度比较)
总结
getMatchData
虽然看起来简单,但它在 Lodash 内部匹配系统中扮演着关键角色。它的设计体现了几个重要的编程思想:
-
数据转换:将对象转换为更适合特定操作的格式,这是函数式编程中的常见模式。
-
性能优化:
- 通过标记值的比较方式来避免不必要的复杂比较
- 使用倒序遍历优化循环性能
- 在原数组上直接修改,避免创建新数组的开销
-
关注点分离:将数据提取与匹配逻辑分开,使代码更清晰。
-
预计算:提前计算和标记值的比较方式,避免在匹配时重复判断。
这种设计方法不仅提高了代码性能,也使得代码更易于理解和维护。即使是一个小小的内部工具函数,也能从中学到很多优秀的代码设计思想。