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

相关推荐
WeiXiao_Hyy33 分钟前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
吃杠碰小鸡1 小时前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone1 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09011 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农1 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king2 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳2 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵3 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星3 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_3 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js