实现 lodash.get

基础知识

  1. lodash.get 的作用是什么? _.get 函数用于安全地根据指定的路径,从一个嵌套对象中获取属性值。如果路径中的任何一个环节不存在(即为 nullundefined),它不会像原生 JS 那样抛出错误,而是会返回 undefined 或一个你指定的默认值。

    const object = { a: [{ b: { c: 3 } }] };
    
    // 原生方式,如果路径不确定,可能会报错
    // console.log(object.a[0].b.d.e); // TypeError: Cannot read properties of undefined
    
    // lodash.get 方式,非常安全
    // _.get(object, 'a[0].b.c'); // => 3
    // _.get(object, 'a[0].b.d.e'); // => undefined
    // _.get(object, 'a[0].b.d.e', 'default'); // => 'default'
    
  2. 路径 (Path) 的格式 _.get 支持多种路径格式,最常见的是:

    • 字符串路径: 使用点 . 和方括号 [] 来表示层级,例如 'a[0].b.c'
    • 数组路径: 将路径的每一步都作为一个元素放入数组中,例如 ['a', '0', 'b', 'c']
  3. 核心价值 它的核心价值在于防御性编程。在处理来源不确定的数据(如 API 响应)时,我们无法保证数据结构总是符合预期。使用 _.get 可以让我们编写出更健壮、更简洁的代码,避免大量的 && 或可选链 ?. 操作符的堆砌。

核心思路

我们的目标是创建一个函数 get(object, path, defaultValue),它能模拟原生 _.get 的行为。核心思路可以概括为“循路前进,遇阻则退”

  1. 路径处理: 首先,需要将各种格式的路径(如 'a[0].b.c')统一处理成一种标准格式,例如一个由属性键组成的数组 ['a', '0', 'b', 'c']。这是最关键的预处理步骤。正则表达式是处理这种字符串的有效工具。

  2. 迭代取值:

    • 将传入的对象 object 作为我们的“当前位置”。
    • 遍历标准化的路径数组(['a', '0', 'b', 'c'])。
    • 在每一步,尝试从“当前位置”取出下一个键对应的属性值(例如,从 object 中取 'a',再从 object.a 中取 '0'...)。
    • 将取出的值更新为新的“当前位置”。
  3. 安全检查 (遇阻则退): 在迭代的每一步,都必须检查“当前位置”是否为 nullundefined。一旦是,就说明路径中断了,无法再继续前进。此时应立即停止遍历,并返回 defaultValue (如果提供了) 或 undefined

  4. 返回结果: 如果成功遍历完整个路径数组,那么最后的“当前位置”就是我们想要的目标值,将其返回。

代码实现

下面是一个兼容字符串和数组路径的 get 函数实现。

/**
 * 安全地获取嵌套对象的属性值
 * @param {object} object - 要查询的对象
 * @param {string | Array<string>} path - 属性路径
 * @param {any} [defaultValue] - 如果解析值为 undefined,则返回此默认值
 * @returns {any} - 返回解析到的值,否则返回默认值或 undefined
 */
function get(object, path, defaultValue) {
  // 1. 将字符串路径转换为数组路径
  // 正则表达式用于匹配点和方括号,并清理掉多余的字符
  const pathArray = Array.isArray(path)
    ? path
    : path.replace(/\[/g, '.').replace(/\]/g, '').split('.');

  // 2. 使用 reduce 进行迭代取值
  let result = pathArray.reduce((acc, key) => {
    // 3. 在每一步都检查 acc 是否为有效的对象
    // 如果 acc 是 null 或 undefined,则返回 undefined,中断后续的取值
    return acc ? acc[key] : undefined;
  }, object); // 4. 初始值 acc 为传入的 object

  // 5. 如果最终结果为 undefined,则返回 defaultValue
  return result === undefined ? defaultValue : result;
}

代码解析

  1. 路径转换(pathArray = ...)

    • 数组判断Array.isArray(path) ? path : ... 首先检查 path 是否为数组,若是则直接使用;若不是则视为字符串路径进行处理。
    • 正则预处理
      • path.replace(/\[/g, '.') 将所有左方括号 [ 替换为点号 .,例如 'a[0]' 变为 'a.0]'
      • path.replace(/\]/g, '') 移除所有右方括号 ],例如 'a.0]' 变为 'a.0'
    • 分割路径:通过 .split('.') 将处理后的字符串按点号分割成路径键数组,例如 'a[0].b.c' 转换为 ['a', '0', 'b', 'c']
  2. reduce 迭代(pathArray.reduce(...))

    • 使用 reduce 折叠数组:利用 Array.prototype.reduce 方法将路径数组“折叠”为单一结果,迭代处理每个路径键。
    • 回调函数结构(acc, key) => ...,其中 acc 为累加器(上一步的取值结果),key 为当前处理的路径键。
    • 初始值设置reduce 的第二个参数为 object,确保首次迭代时 acc 为目标对象,key 为路径数组第一个元素。
  3. 安全取值逻辑(acc ? acc[key] : undefined)

    • 核心安全检查:每次迭代先判断 acc 是否存在(非 null/undefined),避免访问 undefined 属性时报错。
    • 路径中断处理:若 acc 不存在,直接返回 undefined,后续迭代的 acc 始终为 undefined,安全终止取值链。
    • 取值传递:若 acc 存在,取 acc[key] 作为下次迭代的 acc,逐步深入对象属性。
  4. 结果处理与默认值返回

    • 获取最终结果reduce 执行完毕后,result 为路径最终指向的属性值(若路径完整)或 undefined(若路径中断)。
    • 默认值判断return result === undefined ? defaultValue : result,当结果为 undefined 时返回用户指定的 defaultValue,否则返回实际取值。

使用示例

const user = {
  id: 1,
  info: {
    name: '张三',
    address: {
      city: '北京',
      coords: [116.404, 39.915],
    },
  },
  posts: null,
};

console.log(get(user, 'info.name')); // '张三'
console.log(get(user, 'info.address.city')); // '北京'
console.log(get(user, 'info.address.coords[1]')); // 39.915
console.log(get(user, ['info', 'address', 'coords', '0'])); // 116.404

// 测试路径不存在的情况
console.log(get(user, 'info.age')); // undefined
console.log(get(user, 'info.address.zipcode', '邮编未提供')); // '邮编未提供'

// 测试中间路径为 null 的情况
console.log(get(user, 'posts[0].title', '无文章')); // '无文章'
Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""