Skip to content

第 14 题:实现 lodash 的_.get #20

@lgwebdream

Description

@lgwebdream
Owner

欢迎在下方发表您的优质见解

Activity

Genzhen

Genzhen commented on Jun 23, 2020

@Genzhen
Collaborator

在 js 中经常会出现嵌套调用这种情况,如 a.b.c.d.e,但是这么写很容易抛出异常。你需要这么写 a && a.b && a.b.c && a.b.c.d && a.b.c.d.e,但是显得有些啰嗦与冗长了。特别是在 graphql 中,这种嵌套调用更是难以避免。
这时就需要一个 get 函数,使用 get(a, 'b.c.d.e') 简单清晰,并且容错性提高了很多。

1)代码实现

function get(source, path, defaultValue = undefined) {
  // a[3].b -> a.3.b -> [a,3,b]
 // path 中也可能是数组的路径,全部转化成 . 运算符并组成数组
  const paths = path.replace(/\[(\d+)\]/g, ".$1").split(".");
  let result = source;
  for (const p of paths) {
    // 注意 null 与 undefined 取属性会报错,所以使用 Object 包装一下。
    result = Object(result)[p];
    if (result == undefined) {
      return defaultValue;
    }
  }
  return result;
}
// 测试用例
console.log(get({ a: null }, "a.b.c", 3)); // output: 3
console.log(get({ a: undefined }, "a", 3)); // output: 3
console.log(get({ a: null }, "a", 3)); // output: 3
console.log(get({ a: [{ b: 1 }] }, "a[0].b", 3)); // output: 1

2)代码实现
不考虑数组的情况

const _get = (object, keys, val) => {
 return keys.split(/\./).reduce(
  (o, j)=>( (o || {})[j] ), 
  object
 ) || val
}
console.log(get({ a: null }, "a.b.c", 3)); // output: 3
console.log(get({ a: undefined }, "a", 3)); // output: 3
console.log(get({ a: null }, "a", 3)); // output: 3
console.log(get({ a: { b: 1 } }, "a.b", 3)); // output: 1
Genzhen

Genzhen commented on Jun 23, 2020

@Genzhen
Collaborator
//匹配'.'或者'[]'的深层属性
const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/
//匹配普通属性:数字+字母+下划线
const reIsPlainProp = /^\w*$/
const reEscapeChar = /\\(\\)?/g
const rePropName = RegExp(
    //匹配没有点号和括号的字符串,'\\]'为转义']' 例如:'a'
    '[^.[\\]]+' + '|' +
    // Or match property names within brackets.
    '\\[(?:' +
    // 匹配一个非字符的表达式,例如 '[index]'
    '([^"\'][^[]*)' + '|' +
    // 匹配字符串,例如 '["a"]'
    '(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
    ')\\]' + '|' +
    '(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))'
    , 'g')

function get(object, path, defaultValue) {
    let result;
    if (object == null) return result;
    if (!Array.isArray(path)) {
        //是否为key
        const type = typeof path
        if (type == 'number' || type == 'boolean' || path == null || toString.call(path) == '[object Symbol]' || reIsPlainProp.test(path) || !reIsDeepProp.test(path)) {
            path = [path];
        } else {
            //字符串转化为数组
            const tmp = []
            //第一个字符是'.'
            if (path.charCodeAt(0) === '.') {
                tmp.push('')
            }
            path.replace(rePropName, (match, expression, quote, subString) => {
                let key = match
                if (quote) {
                    key = subString.replace(reEscapeChar, '$1')
                } else if (expression) {
                    key = expression.trim()
                }
                tmp.push(key)
            })
            path = tmp;
        }
    }
    //转化为数组后的通用部分
    let index = 0
    const length = path.length
    while (object != null && index < length) {
        let value = path[index++];
        object = object[value]
    }
    result = (index && index == length) ? object : undefined
    return result === undefined ? defaultValue : result
}
huzedong2015

huzedong2015 commented on Aug 28, 2020

@huzedong2015
function _get(obj, path, defaultVal) {
    let newPath = path;
    let result = { ...obj };

    if (typeof path === "string") {
        if (path.includes(".")) {
            // 如果含有数组选项
            if (path.includes("[")) {
                newPath = newPath.replace(/\[(\w)+\]/g, ".$1");
            }

            newPath = newPath.split(".");
        } else {
            newPath = [path];
        }
    }

    for (let i = 0; i < newPath.length; i++) {
        let currentKey = newPath[i];

        result = result[currentKey];

        if (typeof result === "undefined") {
            result = defaultVal;

            break;
        }
    }

    return result;
};

const data = {
    a: {
        b: [1, 2, 3]
    }
};

console.log(_get(data, "a.b[4]", 20));

ES2020 可以链式 & 空值合并运算

data?.b?.[4] ?? 20

huzedong2015

huzedong2015 commented on Aug 28, 2020

@huzedong2015

在 js 中经常会出现嵌套调用这种情况,如 a.b.c.d.e,但是这么写很容易抛出异常。你需要这么写 a && a.b && a.b.c && a.b.c.d && a.b.c.d.e,但是显得有些啰嗦与冗长了。特别是在 graphql 中,这种嵌套调用更是难以避免。
这时就需要一个 get 函数,使用 get(a, 'b.c.d.e') 简单清晰,并且容错性提高了很多。

1)代码实现

function get(source, path, defaultValue = undefined) {
  // a[3].b -> a.3.b -> [a,3,b]
 // path 中也可能是数组的路径,全部转化成 . 运算符并组成数组
  const paths = path.replace(/\[(\d+)\]/g, ".$1").split(".");
  let result = source;
  for (const p of paths) {
    // 注意 null 与 undefined 取属性会报错,所以使用 Object 包装一下。
    result = Object(result)[p];
    if (result == undefined) {
      return defaultValue;
    }
  }
  return result;
}
// 测试用例
console.log(get({ a: null }, "a.b.c", 3)); // output: 3
console.log(get({ a: undefined }, "a", 3)); // output: 3
console.log(get({ a: null }, "a", 3)); // output: 3
console.log(get({ a: [{ b: 1 }] }, "a[0].b", 3)); // output: 1

2)代码实现
不考虑数组的情况

const _get = (object, keys, val) => {
 return keys.split(/\./).reduce(
  (o, j)=>( (o || {})[j] ), 
  object
 ) || val
}
console.log(get({ a: null }, "a.b.c", 3)); // output: 3
console.log(get({ a: undefined }, "a", 3)); // output: 3
console.log(get({ a: null }, "a", 3)); // output: 3
console.log(get({ a: { b: 1 } }, "a.b", 3)); // output: 1

path参数还有数组的可能

sh-tengfei

sh-tengfei commented on Jan 13, 2021

@sh-tengfei
function _get(obj, path, defaultVal) {
    let newPath = path;
    let result = { ...obj };

    if (typeof path === "string") {
        if (path.includes(".")) {
            // 如果含有数组选项
            if (path.includes("[")) {
                newPath = newPath.replace(/\[(\w)+\]/g, ".$1");
            }

            newPath = newPath.split(".");
        } else {
            newPath = [path];
        }
    }

    for (let i = 0; i < newPath.length; i++) {
        let currentKey = newPath[i];

        result = result[currentKey];

        if (typeof result === "undefined") {
            result = defaultVal;

            break;
        }
    }

    return result;
};

const data = {
    a: {
        b: [1, 2, 3]
    }
};

console.log(_get(data, "a.b[4]", 20));

newPath.replace(/[(\w)+]/g, ".$1"); 这里是否应该是 newPath.replace(/[(\d+)]/g, ".$1");

Xiaolong96

Xiaolong96 commented on Mar 22, 2021

@Xiaolong96
function get(target, path, defaultValue) {
  let keys = path.replace(/\[(\d+)\]/g, ".$1").split(".");
  return keys.reduce((t, key) => (t || {})[key], target) ?? defaultValue;
}
qzruncode

qzruncode commented on Apr 14, 2021

@qzruncode
这道题还是非常不错的,可以锻炼正则和错误处理
function get(obj, path) {
  const keys = path.split('.');
  let res = obj;
  for(let i = 0; i < keys.length; i++) {
    const reg = /^(\w+)\[(\d+)\]$/;
    const key = keys[i];
    if(reg.test(key)) { // 数组索引
      const tmp = key.match(reg);
      const k = tmp[1]; // 键
      const i = Number(tmp[2]); // 索引
      let isError = false;
      try {
        res = res[k][i];
      } catch (error) {
        isError = true;
      } finally {
        if(res == undefined) isError = true;
      }
      if(isError) break;
    }else { // 对象键
      res = res[key];
      if(res == undefined) return '';;
    }
  }
  return res;
}
get({ 'a': [{ 'b': { 'c': 3 } }] }, 'a[0].b.c');
fanerge

fanerge commented on May 17, 2021

@fanerge
function lensProp(obj = {}, path = '') {
  if (typeof obj !== 'object' || obj === null) {
    obj = {}
  }
  let props = path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
  for (let i = 0; i < props.length; i++) {
    if (typeof obj[props[i]] === 'undefined') {
      return void 0;
    } else {
      // debugger
      if (typeof obj[props[i]] === 'object' && obj !== null) {
        obj = obj[props[i]]
      } else {
        return i === props.length - 1 ? obj[props[i]] : void 0;
      }
    }
  }

  return obj;
}
var obj6 = {
  name: 'yzf',
  children: [{
    name: 'yy',
    age: 1,
    children: [
      {
        name: 'yyy',
        age: 1,
        children: []
      }
    ]
  }, {
    name: 'yy1',
    age: 8,
    children: []
  }],
  other: {
    year: 29
  }
}
// console.log(lensProp(obj6, 'children.0.name'));
// console.log(lensProp(obj6, 'children[0].name'));
jonny-wei

jonny-wei commented on May 18, 2021

@jonny-wei
const get = function (object, path, defaultValue = undefined) {
  const paths = Array.isArray(path) ? path : path.replace(/\[(\d+)\]/g, ".$1").split("."); 
  let result = object; 
  for (const key of paths) {
    result = Object(result)[key];
    if (result === undefined) {
      return defaultValue;
    }
  }
  return result;
};
Luoyuda

Luoyuda commented on Jun 10, 2021

@Luoyuda
function get(obj, path, defaultValue){
        let data = obj
        path = path.replace(/\[(\d+)\]/ig, ".$1").split('.')
        for(let key of path){
            if(data[key]){
                data = data[key]
            }else{
                return defaultValue
            }
        }
        return data
    }
    // 测试用例
    console.log(get({ a: null }, "a.b.c", 3)); // output: 3
    console.log(get({ a: undefined }, "a", 3)); // output: 3
    console.log(get({ a: null }, "a.b", 3)); // output: 3
    console.log(get({ a: [{ b: 1 }] }, "a[0].b", 3)); // output: 1
    console.log(get({ a: { b : { c: 2 } } }, "a.b.c", 3)); // output: 2
Evllis

Evllis commented on Aug 11, 2021

@Evllis
/**
 * @description 仿lodash的get方法,优化属性嵌套地址狱
 * @param { Object } source 要检测的源变量
 * @param { String } path 变量路径
 * @param { Any } dValue 如果未匹配到,则返回默认值
 * @return { Any } 返回目标值或者默认值
 */
const validationValue = (source, path, dValue = undefined) => {
    const paths = Object.prototype.toString.call(path) === '[object Array]'
                    ? path
                    : path.replace(/\[(\d+)\]/g, '.$1').split('.');
    for (let i of paths) {
        source = source?.[i];
        if (source === undefined) return dValue;
    }
    return source;
}

console.log(1111, validationValue({ a: null }, 'a.b.c', 3))
console.log(2222, validationValue({ a: undefined }, 'a', 3))
console.log(3333, validationValue({ a: null }, 'a.b', 3))
console.log(4444, validationValue({ a: [{ b: 1 }] }, 'a[0].b', 3))
console.log(5555, validationValue({ a: { b : { c: 2 } } }, 'a.b.c', 3))
console.log(6666, validationValue({ 'a': [{ 'b': { 'c': 3 } }] }, 'a[0].b.c', 8))
Weibozzz

Weibozzz commented on Sep 9, 2021

@Weibozzz

在 js 中经常会出现嵌套调用这种情况,如 a.b.c.d.e,但是这么写很容易抛出异常。你需要这么写 a && a.b && a.b.c && a.b.c.d && a.b.c.d.e,但是显得有些啰嗦与冗长了。特别是在 graphql 中,这种嵌套调用更是难以避免。
这时就需要一个 get 函数,使用 get(a, 'b.c.d.e') 简单清晰,并且容错性提高了很多。

1)代码实现

function get(source, path, defaultValue = undefined) {
  // a[3].b -> a.3.b -> [a,3,b]
 // path 中也可能是数组的路径,全部转化成 . 运算符并组成数组
  const paths = path.replace(/\[(\d+)\]/g, ".$1").split(".");
  let result = source;
  for (const p of paths) {
    // 注意 null 与 undefined 取属性会报错,所以使用 Object 包装一下。
    result = Object(result)[p];
    if (result == undefined) {
      return defaultValue;
    }
  }
  return result;
}
// 测试用例
console.log(get({ a: null }, "a.b.c", 3)); // output: 3
console.log(get({ a: undefined }, "a", 3)); // output: 3
console.log(get({ a: null }, "a", 3)); // output: 3
console.log(get({ a: [{ b: 1 }] }, "a[0].b", 3)); // output: 1

2)代码实现
不考虑数组的情况

const _get = (object, keys, val) => {
 return keys.split(/\./).reduce(
  (o, j)=>( (o || {})[j] ), 
  object
 ) || val
}
console.log(get({ a: null }, "a.b.c", 3)); // output: 3
console.log(get({ a: undefined }, "a", 3)); // output: 3
console.log(get({ a: null }, "a", 3)); // output: 3
console.log(get({ a: { b: 1 } }, "a.b", 3)); // output: 1

改进版本

function get (source, path, defaultValue = undefined) {
  const paths = Array.isArray(path) ? path
    : path.replace(/\[(\d+)\]/g, '.$1')
      .split('.')
      .filter(Boolean)
  let result = source
  for (const p of paths) {
    result = Object(result)[p]
    if (result == undefined) {
      return defaultValue
    }
  }
  return result
}
console.log(
  get({ a: [{ b: 1 }] }, 'a[0].b', 3)
) // output: 1
console.log(
  get({ a: [{ b: 1 }] }, '.a[0].b', 3)
) // output: 1
console.log(
  get({ a: [{ b: 1 }] }, ['a',0,'b'], 3)
) // output: 1
safarishi

safarishi commented on Oct 14, 2021

@safarishi
function get(obj, path, defaults) {
  try {
    const val = path.split('.')
      .map((item) => item.split(/\[(\d)\]/).filter(Boolean)).flat().reduce((acc, v) => acc[v], obj)

    if (typeof defaults !== 'undefined' && val === undefined) {
      return defaults
    }

    return val
  } catch (_) {
    return defaults
  }
}
captain-kuan

captain-kuan commented on Dec 14, 2022

@captain-kuan
function get(target: object, propStr: string, defaultValue: any = undefined) {
    const props = propStr.split('.')
    let t: any = target
    for (const prop of props) {
        if (t == null) break
        t = t[prop]
    }
    if (t === undefined) return defaultValue
    return t
}
yaohui-li

yaohui-li commented on Aug 25, 2023

@yaohui-li

const getPath = (path) => {
const paths = [']', '[', '.']
if (Array.isArray(path)) return path;
return Array.from(path).filter((str) => !paths.includes(str))
};
const getObjectValue = (object, path, defaultValue) => {
const objectPath = getPath(path);
let target = object
let index = 0;
while (index < objectPath.length) {
target = target[objectPath[index++]];
if (target === undefined) break;
}
return index === objectPath.length ? target : defaultValue;
};

Kisthanny

Kisthanny commented on Mar 20, 2024

@Kisthanny
/**
 * 实现lodash的_.get
 * lodash官方文档
 * Gets the value at path of object. If the resolved value is undefined, 
 * the defaultValue is returned in its place.
 * 
 * Arguments
object (Object): The object to query.
path (Array|string): The path of the property to get.
[defaultValue] (*): The value returned for undefined resolved values.

 * Returns
(*): Returns the resolved value.

 * 官方示例
var object = { 'a': [{ 'b': { 'c': 3 } }] };
 
_.get(object, 'a[0].b.c');
// => 3
 
_.get(object, ['a', '0', 'b', 'c']);
// => 3
 
_.get(object, 'a.b.c', 'default');
// => 'default'
 */

class Lodash {
  get(object, path, defaultValue = undefined) {
    if (typeof object !== "object") {
      throw new Error("first argument of get should be an object");
    }
    if (path instanceof Array) {
      return this._keysReduce(object, path, defaultValue);
    }
    if (typeof path === "string") {
      const keys = path.split(/[\.|\[|\]]/).filter((key) => Boolean(key));
      return this._keysReduce(object, keys, defaultValue);
    }
  }
  _keysReduce(object, path, defaultValue) {
    return path.reduce((pre, cur) => {
      if (typeof cur !== "string") {
        throw new Error("path should be an Array of Strings");
      }
      if (pre === undefined) {
        return defaultValue;
      }
      return pre[cur];
    }, object);
  }
}

const _ = new Lodash();
const object = { a: [{ b: { c: 3 } }] };

console.log(_.get(object, "a[0].b.c"));

console.log(_.get(object, ["a", "0", "b", "c"]));

console.log(_.get(object, "a.b.c", "default"));
aaronxdd

aaronxdd commented on Sep 7, 2024

@aaronxdd

const obGet = (ob, path, defaltValue) => {
const kes = Array.isArray(path) ? path : path.split('.');
return kes.reduces((pre, current) => pre[current], ob) || defaltValue;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @lgwebdream@safarishi@sh-tengfei@Evllis@huzedong2015

        Issue actions

          第 14 题:实现 lodash 的_.get · Issue #20 · lgwebdream/FE-Interview