Lodash源码阅读-hasPath

Lodash 源码阅读-hasPath

概述

hasPath 是 Lodash 的内部工具函数,用于检查对象中是否存在指定路径。它是 _.has_.hasIn 这两个公共 API 的核心实现,能够处理各种格式的路径查询并妥善处理各种边界情况。

前置学习

依赖函数

  • castPath :将各种格式的路径转换成标准数组格式,例如 "a.b.c" 转为 ["a", "b", "c"]
  • toKey:将路径中的各个部分转换为有效的属性键,特别处理了负零(-0)的情况
  • isLength:判断一个值是否是合法的数组长度(非负整数且小于等于最大安全整数)
  • isIndex:判断一个值是否是合法的数组索引(非负整数且小于给定的长度)
  • isArray:检查一个值是否为数组
  • isArguments:检查一个值是否为函数的 arguments 对象

技术知识

  • 属性路径 :JavaScript 中表示嵌套对象路径的方式,如 user.profile.nameuser['profile']['name']
  • 对象属性访问:JavaScript 中通过点号或方括号访问对象属性的方法
  • 数组索引和稀疏数组:数组索引的特性及稀疏数组(含"空位"的数组)的概念
  • 类数组对象:具有数字索引和 length 属性但不是真正数组的对象

源码实现

javascript 复制代码
function hasPath(object, path, hasFunc) {
  path = castPath(path, object);

  var index = -1,
    length = path.length,
    result = false;

  while (++index < length) {
    var key = toKey(path[index]);
    if (!(result = object != null && hasFunc(object, key))) {
      break;
    }
    object = object[key];
  }
  if (result || ++index != length) {
    return result;
  }
  length = object == null ? 0 : object.length;
  return (
    !!length &&
    isLength(length) &&
    isIndex(key, length) &&
    (isArray(object) || isArguments(object))
  );
}

实现思路

hasPath 的实现分为两个主要阶段:

  1. 路径逐级查找 :首先将路径转换为标准数组格式,然后从第一级开始逐级检查对象中是否存在该属性。如果任一级别查找失败,立即返回 false

  2. 特殊数组处理:如果常规查找在最后一级失败,函数会进行额外检查:判断当前对象是否是数组或类数组对象,以及最后一级路径是否是一个有效的数组索引。这主要是为了处理数组中的"空位"情况。

源码解析

路径准备

javascript 复制代码
path = castPath(path, object);

var index = -1,
  length = path.length,
  result = false;

这部分代码完成初始化工作:

  1. 使用 castPath 将路径转换为标准数组格式,处理不同的输入形式:

    javascript 复制代码
    castPath("a.b.c", obj); // 转为 ['a', 'b', 'c']
    castPath(["a", "b", "c"], obj); // 保持不变
    castPath("a.b", { "a.b": 42 }); // 转为 ['a.b'],因为这是直接属性
  2. 设置初始变量:

    • index: 当前处理的路径级别,初始为 -1
    • length: 路径数组的长度
    • result: 查找结果,默认为 false

路径查找循环

javascript 复制代码
while (++index < length) {
  var key = toKey(path[index]);
  if (!(result = object != null && hasFunc(object, key))) {
    break;
  }
  object = object[key];
}

这个循环是函数的核心部分:

  1. ++index < length 确保循环在路径范围内进行
  2. toKey(path[index]) 将当前路径段转换为有效属性键
  3. object != null 检查当前对象是否存在(不是 nullundefined
  4. hasFunc(object, key) 使用传入的函数检查键是否存在于对象中
  5. 如果检查通过,更新 resulttrue,并深入下一层 object = object[key]
  6. 如果检查失败,设置 resultfalse 并退出循环

hasFunc 是从外部传入的属性检查函数:

  • _.has 使用 baseHas(只检查对象自身属性)
  • _.hasIn 使用 baseHasIn(检查对象自身及继承属性)

示例:

javascript 复制代码
var user = {
  profile: {
    name: "张三",
  },
};

// 检查 user.profile.name 是否存在
hasPath(user, ["profile", "name"], (obj, key) => key in obj);
// 第一轮:检查 'profile' 是否存在于 user 中 ✓
// 第二轮:检查 'name' 是否存在于 user.profile 中 ✓
// 结果:true

特殊情况处理

javascript 复制代码
if (result || ++index != length) {
  return result;
}
length = object == null ? 0 : object.length;
return (
  !!length &&
  isLength(length) &&
  isIndex(key, length) &&
  (isArray(object) || isArguments(object))
);

这部分处理两种情况:

  1. 如果路径查找成功(resulttrue)或未完成整个路径的遍历(++index != length),直接返回 result

  2. 如果查找恰好在最后一级失败,进行特殊检查:

    • 检查当前对象是否有 length 属性且值有效
    • 检查 length 是否是合法的数组长度
    • 检查最后一个键是否是合法的数组索引(在 0 到 length-1 范围内)
    • 确认当前对象是否是数组或类数组对象

这个特殊处理主要是为了处理数组的"空位"情况:

javascript 复制代码
var arr = [];
arr[2] = "值"; // arr 现在是 [empty, empty, "值"]

// 检查 arr[1] 是否存在
hasPath(arr, [1], (obj, key) => key in obj);
// 常规检查:false(索引 1 是空的)
// 特殊检查:
// - arr.length 存在且为 3 ✓
// - 3 是合法的数组长度 ✓
// - 键 1 是合法的索引(在 0-2 范围内)✓
// - arr 是数组 ✓
// 最终结果:true

总结

hasPath 作为 Lodash 属性检查功能的核心实现,展示了几个重要的编程技巧:

  1. 输入标准化:将不同格式的输入转换为统一格式,增强函数的兼容性
  2. 逐级探索:通过循环逐步检查属性路径,发现问题立即终止,提高效率
  3. 边界情况处理:对数组的特殊处理,确保在复杂情况下也能得到符合预期的结果
  4. 函数参数化:通过传入不同的检查函数,实现灵活的属性检查策略

这些技巧在处理复杂数据结构、支持多种输入格式或需要精确控制行为时非常有用。通过 _.has_.hasIn 这两个公共 API,我们可以利用 hasPath 的功能编写更加健壮和安全的代码,避免因属性访问错误导致的程序崩溃。

相关推荐
wuhen_n10 小时前
JavaScript数据结构深度解析:栈、队列与树的实现与应用
前端·javascript
我是一只puppy10 小时前
使用AI进行代码审查
javascript·人工智能·git·安全·源代码管理
颜酱10 小时前
从二叉树到衍生结构:5种高频树结构原理+解析
javascript·后端·算法
狗哥哥11 小时前
微前端路由设计方案 & 子应用管理保活
前端·架构
前端大卫11 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘12 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare12 小时前
浅浅看一下设计模式
前端
Lee川12 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix12 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人12 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc