Lodash源码阅读-compareAscending

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 函数实现了一个稳定的比较逻辑,用于确定两个值的大小关系。它的核心思路是:

  1. 首先判断两个值是否相等,相等则直接返回 0
  2. 对两个值的特性进行分析(是否为 undefined、null、Symbol、NaN)
  3. 根据值的特性和大小关系,决定返回 1(第一个值更大)或 -1(第一个值更小)
  4. 考虑特殊情况的处理,确保排序稳定性

源码解析

函数一开始进行相等性检查:

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 中的各种边界情况。

相关推荐
@大迁世界6 小时前
Firefox 本地模式:localhost 再见了 ?
前端·架构
米米商聊8 小时前
同名附件为什么被 Map 覆盖?重复键与一对多索引
前端·javascript
echome8889 小时前
Python 异常处理:try、else、finally 的正确搭配
开发语言·前端·python·异常处理·编程技巧
时间的情敌9 小时前
OpenLayers 实战|Vue3 + OpenLayers 完整工程案例(加载天地图、要素、弹窗、绘制、WFS)
前端·openlayers
陈随易9 小时前
下载神器!Motrix 停更三年后重写回归
前端·后端·程序员
前端的日常10 小时前
一个人+AI做情侣食谱小程序,0 费用的服务器
前端·后端
计算机魔术师10 小时前
谷歌搜索广告踩刹车,AI广告成为救命稻草
前端
总要学点什么11 小时前
第81篇:数据可视化-PM2.5分布——让圆点的胖瘦替每座城市报出浓度
前端·信息可视化·echarts·cesium·空间分析
IT_陈寒11 小时前
Vue的v-for里用了个临时变量,页面直接挂了
前端·人工智能·后端
hunter_07611 小时前
一次 iOS 真机签名 409 的完整排查:证书、设备、描述文件为什么总是"对不上"
前端