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 的功能编写更加健壮和安全的代码,避免因属性访问错误导致的程序崩溃。

相关推荐
ZXT1 分钟前
WebWorker&sharedWorker
前端
ZXT5 分钟前
性能优化
前端
鹿屿二向箔25 分钟前
如何开发 HTML 游戏
前端·游戏·html
介si啥呀~29 分钟前
Vuex 的使用场景和使用方法
前端·javascript·vue.js·vuex
远方小镇32 分钟前
抖音开放平台-业务架构招前端
前端·javascript·面试
申朝先生1 小时前
es6的箭头函数与普通函数的区别,箭头函数的this通常指向哪里,箭头函数可以用作构造函数吗?
前端·ecmascript·es6
TheK1 小时前
MCP到底是什么
前端·人工智能
前端飞天猪1 小时前
学习笔记:从手动到自动,让版本号管理成为团队的高效习惯
前端·github
关二哥拉二胡1 小时前
前端的 AI 应用开发系列四:智能体Agent的发展历程
前端·javascript
best6661 小时前
JS数组遍历方法这么多,for/forEach/forof我该怎么选?
前端·javascript