Lodash 源码阅读-compareAscending
概述
compareAscending
是 Lodash 内部的一个比较函数,用于实现升序排序。它能处理 JavaScript 中各种特殊值的比较(如 null
、undefined
、Symbol
等),确保排序结果符合预期。
前置学习
-
依赖函数:
isSymbol
: 用于判断一个值是否为 Symbol 类型
-
技术知识:
- JavaScript 中的值比较
- JavaScript 中的特殊值(undefined、null、NaN、Symbol)
- 反射性质(reflexivity):即值是否等于自身,NaN 是唯一不等于自身的值
源码实现
js
function compareAscending(value, other) {
if (value !== other) {
var valIsDefined = value !== undefined,
valIsNull = value === null,
valIsReflexive = value === value,
valIsSymbol = isSymbol(value);
var othIsDefined = other !== undefined,
othIsNull = other === null,
othIsReflexive = other === other,
othIsSymbol = isSymbol(other);
if (
(!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
(valIsSymbol &&
othIsDefined &&
othIsReflexive &&
!othIsNull &&
!othIsSymbol) ||
(valIsNull && othIsDefined && othIsReflexive) ||
(!valIsDefined && othIsReflexive) ||
!valIsReflexive
) {
return 1;
}
if (
(!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
(othIsSymbol &&
valIsDefined &&
valIsReflexive &&
!valIsNull &&
!valIsSymbol) ||
(othIsNull && valIsDefined && valIsReflexive) ||
(!othIsDefined && valIsReflexive) ||
!othIsReflexive
) {
return -1;
}
}
return 0;
}
实现思路
compareAscending
函数实现了一个稳定的比较逻辑,用于确定两个值的大小关系。它的核心思路是:
- 首先判断两个值是否相等,相等则直接返回 0
- 对两个值的特性进行分析(是否为 undefined、null、Symbol、NaN)
- 根据值的特性和大小关系,决定返回 1(第一个值更大)或 -1(第一个值更小)
- 考虑特殊情况的处理,确保排序稳定性
源码解析
函数一开始进行相等性检查:
js
if (value !== other) {
// 如果不相等,进入复杂比较逻辑
} else {
return 0; // 如果完全相等,直接返回0表示相等
}
接下来定义了 8 个变量来标记两个比较值的特性:
js
// 对第一个值的特性分析
var valIsDefined = value !== undefined, // 值不是undefined
valIsNull = value === null, // 值是null
valIsReflexive = value === value, // 值等于自身(排除NaN)
valIsSymbol = isSymbol(value); // 值是Symbol类型
// 对第二个值做同样的特性分析
var othIsDefined = other !== undefined,
othIsNull = other === null,
othIsReflexive = other === other,
othIsSymbol = isSymbol(other);
这里特别注意valIsReflexive
和othIsReflexive
的作用是检测 NaN,因为在 JavaScript 中只有 NaN 不等于自身:NaN === NaN
返回false
。
然后是两个复杂的条件判断,第一个判断何时返回 1(表示 value 大于 other):
js
if (
// 条件1: 普通值比较,value确实大于other
(!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
// 条件2: value是Symbol,而other是普通值(非null/Symbol/undefined/NaN)
(valIsSymbol &&
othIsDefined &&
othIsReflexive &&
!othIsNull &&
!othIsSymbol) ||
// 条件3: value是null,other是除undefined和NaN外的值
(valIsNull && othIsDefined && othIsReflexive) ||
// 条件4: value是undefined,other不是NaN
(!valIsDefined && othIsReflexive) ||
// 条件5: value是NaN
!valIsReflexive
) {
return 1;
}
第二个判断何时返回-1(表示 value 小于 other):
js
if (
// 条件1: 普通值比较,value确实小于other
(!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
// 条件2: other是Symbol,而value是普通值
(othIsSymbol &&
valIsDefined &&
valIsReflexive &&
!valIsNull &&
!valIsSymbol) ||
// 条件3: other是null,value是除undefined和NaN外的值
(othIsNull && valIsDefined && valIsReflexive) ||
// 条件4: other是undefined,value不是NaN
(!othIsDefined && valIsReflexive) ||
// 条件5: other是NaN
!othIsReflexive
) {
return -1;
}
让我们详细拆解这些条件:
返回 1 的条件 1 :!othIsNull && !othIsSymbol && !valIsSymbol && value > other
- 确保 other 不是 null
- 确保 other 不是 Symbol
- 确保 value 不是 Symbol
- 然后用普通的大于号比较 value 和 other
例如:compareAscending(5, 3)
满足这个条件,返回 1
返回 1 的条件 2 :valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol
- value 是 Symbol
- other 不是 undefined
- other 不是 NaN
- other 不是 null
- other 不是 Symbol
例如:compareAscending(Symbol(), 42)
满足这个条件,返回 1(表示 Symbol > 普通值)
返回 1 的条件 3 :valIsNull && othIsDefined && othIsReflexive
- value 是 null
- other 不是 undefined
- other 不是 NaN
例如:compareAscending(null, 42)
满足这个条件,返回 1(表示 null > 普通值)
返回 1 的条件 4 :!valIsDefined && othIsReflexive
- value 是 undefined
- other 不是 NaN
例如:compareAscending(undefined, null)
满足这个条件,返回 1(表示 undefined > null)
返回 1 的条件 5 :!valIsReflexive
- value 是 NaN
例如:compareAscending(NaN, undefined)
满足这个条件,返回 1(表示 NaN > undefined)
对于返回-1 的条件,逻辑是类似的,只是互换了 value 和 other 的角色。
总结这个函数实现的排序规则:
javascript
NaN > undefined > null > Symbol > 普通值
普通值之间按照 JavaScript 内置的比较规则排序。这种排序策略确保了稳定性,并处理了 JavaScript 中所有可能的值类型。
总结
compareAscending
函数巧妙地解决了 JavaScript 中各种特殊值的比较问题,遵循了以下排序规则:
- 普通值(如数字、字符串)按自然顺序排序
- 特殊值按 NaN > undefined > null > Symbol > 普通值 的顺序排序
这个函数充分体现了 Lodash 处理边界情况的严谨性,在实际开发中,我们也应该注意处理各种特殊值的比较逻辑,以确保程序的健壮性。通过学习这个函数,我们可以了解如何设计一个完善的比较函数,以及如何考虑 JavaScript 中的各种边界情况。