Lodash源码阅读-baseIsEqual

Lodash 源码阅读-baseIsEqual

概述

baseIsEqual 是 Lodash 中实现"深度相等比较"的核心函数。说白了,它就是用来判断两个值是不是"真的相等",不管是简单的数字字符串,还是复杂的嵌套对象和数组,甚至是那些环形引用的结构,它都能正确处理。这个函数是 _.isEqual_.isEqualWith 这两个公开 API 的内部实现基础。

前置学习

依赖函数

  • baseIsEqualDeep:处理复杂数据类型的深度比较,比如对象、数组等需要逐项比较的情况
  • isObjectLike:判断一个值是不是"像对象"(typeof 是 'object' 但不是 null)

技术知识

  • 严格相等比较 :JavaScript 中的 === 操作符,判断两个值是否严格相同
  • NaN 特殊处理 :JS 中 NaN !== NaN 这个奇葩特性及其处理方法
  • 短路优化:通过先判断简单情况来避免不必要的复杂计算
  • 位掩码:使用二进制位来控制函数行为的技巧
  • 递归比较:如何安全地处理嵌套数据结构的比较
  • 循环引用检测:如何避免因循环引用导致的无限递归

源码实现

javascript 复制代码
function baseIsEqual(value, other, bitmask, customizer, stack) {
  if (value === other) {
    return true;
  }
  if (
    value == null ||
    other == null ||
    (!isObjectLike(value) && !isObjectLike(other))
  ) {
    return value !== value && other !== other;
  }
  return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
}

实现思路

baseIsEqual 的思路很聪明,它采用了"分层检查"的策略:

  1. 先看最简单的情况:如果两个值严格相等(用 === 判断),那就直接返回 true
  2. 再处理一些特殊情况:比如有一个值是 null、两个值都不是对象等,这时只有在两个值都是 NaN 的情况下才返回 true
  3. 最后,对于需要深入比较的复杂数据类型(比如对象、数组),就交给专门的 baseIsEqualDeep 函数处理

源码解析

参数说明

javascript 复制代码
function baseIsEqual(value, other, bitmask, customizer, stack) {
  // ...
}

这个函数接收五个参数:

  • valueother:要比较的两个值
  • bitmask:控制比较行为的位掩码,是一个数字,里面的每一位都有特定含义:
    • 1(COMPARE_UNORDERED_FLAG):表示无序比较,主要用于对象属性顺序不重要的场景
    • 2(COMPARE_PARTIAL_FLAG):表示部分比较,允许 othervalue 的子集
    • 组合使用如 3(1|2)表示既无序又部分比较
  • customizer:自定义比较函数,让用户可以定义特殊的比较逻辑
  • stack:用于跟踪已比较的对象,防止循环引用导致的无限递归

严格相等比较 - 快速路径

javascript 复制代码
if (value === other) {
  return true;
}

这是最简单也是最快的检查。对于大多数基本类型(数字、字符串、布尔值等)和引用相同的对象,这一步就能得出结果,避免了后续更复杂的比较。

举几个例子:

javascript 复制代码
// 这些情况在第一步就会返回 true
baseIsEqual(1, 1); // 基本类型相同
baseIsEqual("hello", "hello"); // 字符串相同
baseIsEqual(true, true); // 布尔值相同

let obj = {};
baseIsEqual(obj, obj); // 同一个对象引用

特殊情况处理 - NaN 比较

javascript 复制代码
if (
  value == null ||
  other == null ||
  (!isObjectLike(value) && !isObjectLike(other))
) {
  return value !== value && other !== other;
}

这段代码看着复杂,其实就是在判断:

  1. 如果有一个值是 nullundefined
  2. 或者两个值都不是对象类型

在这些情况下,我们只在一种特殊情况下返回 true:两个值都是 NaN

为什么要这么写?因为在 JavaScript 中,NaN 有个奇葩的特性:它不等于自己!也就是 NaN !== NaN 会返回 true。所以 value !== value 实际上是在判断 value 是不是 NaN

javascript 复制代码
// NaN 的特殊处理
baseIsEqual(NaN, NaN); // true,特殊处理让 NaN 等于 NaN
baseIsEqual(null, undefined); // false,尽管它们双等号(==)为true,但这里不相等
baseIsEqual(42, "42"); // false,不会自动类型转换

深度比较 - 复杂对象处理

javascript 复制代码
return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);

当走到这一步,说明我们需要进行深度比较了。函数将任务委托给 baseIsEqualDeep,同时传入几个重要参数:

  • 要比较的两个值:valueother
  • 控制比较行为的位掩码:bitmask
  • 自定义比较函数:customizer
  • 递归比较的函数:baseIsEqual(就是它自己,用于后续递归)
  • 已比较对象的跟踪栈:stack,防止循环引用导致无限递归

baseIsEqualDeep 会根据值的类型使用不同的策略:

  • 对于数组,使用 equalArrays 逐项比较
  • 对于对象,使用 equalObjects 比较每个属性
  • 对于其他类型(如 Date、RegExp 等),使用 equalByTag 根据其 toString 标签选择合适的比较方法

Stack 参数的作用

stack 参数很关键,它是用来处理循环引用的关键机制。想象一下,如果有两个对象互相引用:

javascript 复制代码
const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;

// 如果没有 stack 参数,这里会无限递归
baseIsEqual(obj1, obj2);

为了避免无限递归,stack 会记录已经比较过的对象对,如果发现当前比较的对象对之前已经比较过,就直接返回之前的比较结果,而不是再次递归比较。

javascript 复制代码
// stack 的大致工作原理
function compareWithStack(a, b, stack = new Map()) {
  // 检查是否已经比较过这对对象
  if (stack.has(a)) {
    return stack.get(a) === b;
  }

  // 记录正在比较的对象对
  stack.set(a, b);

  // 进行实际比较...

  // 比较完成后可以从 stack 中移除
  stack.delete(a);
}

总结

baseIsEqual 是 Lodash 深度比较功能的基石,它解决了 JavaScript 原生比较操作符的局限性。它的精彩之处在于:

  1. 巧妙的分层设计:从简单比较开始,只在必要时才进行复杂比较,大大提高了性能
  2. 灵活的比较策略:通过位掩码控制比较行为,支持有序/无序、完全/部分多种比较模式
  3. 全面的类型处理:不仅处理基本类型,还能正确处理各种复杂的对象类型
  4. 安全的递归实现:通过 Stack 机制避免循环引用导致的无限递归
  5. 可扩展的接口:通过 customizer 允许用户自定义比较逻辑

虽然这只是 Lodash 的一个内部函数,但从它的设计中我们可以学到很多实用技巧:优先处理常见情况、特殊处理边缘案例、分解复杂问题、提供灵活的扩展点。这些思想不仅适用于相等比较,也可以应用到其他复杂算法的设计中。

相关推荐
315356691313 分钟前
一文带你了解二维码扫码的全部用途
前端·后端
七月shi人20 分钟前
用claude3.7,不到1天写了一个工具小程序(11个工具6个游戏)
前端·小程序·ai编程
Billy Qin28 分钟前
Rollup详解
前端·javascript·rollup
夜寒花碎40 分钟前
前端自动化测试一jest基础使用
前端·单元测试·jest
小徐_233344 分钟前
uni-app工程实战:基于vue-i18n和i18n-ally的国际化方案
前端·微信小程序·uni-app
前端小菜鸟一枚s1 小时前
`ConstantPositionProperty` 的使用与应用
前端·javascript·cesium
JohnsonXin1 小时前
怎么使用vue3实现一个优雅的不定高虚拟列表
前端·javascript·css·html5
东望1 小时前
写代码不规范,同事两行泪 😭(工程化如何让团队协作更高效)
javascript·前端工程化
用户837701408811 小时前
前端实现个人信息脱敏(手机号、身份证号、姓名、邮箱)JavaScript代码示例
javascript
17Knight1 小时前
我的个性化 VSCode
前端