Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
lgwebdream opened this issue Jun 19, 2020 · 16 comments
Open

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

lgwebdream opened this issue Jun 19, 2020 · 16 comments
Labels
JavaScript teach_tag 滴滴 company

Comments

@lgwebdream
Copy link
Owner

lgwebdream commented Jun 19, 2020

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

@lgwebdream lgwebdream added JavaScript teach_tag 滴滴 company labels Jun 19, 2020
@Genzhen
Copy link
Collaborator

Genzhen commented Jun 23, 2020

在 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
Copy link
Collaborator

Genzhen commented Jun 23, 2020

//匹配'.'或者'[]'的深层属性
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
}

@Genzhen Genzhen closed this as completed Jul 20, 2020
@Genzhen Genzhen reopened this Jul 29, 2020
@huzedong2015
Copy link

huzedong2015 commented Aug 28, 2020

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
Copy link

在 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
Copy link

sh-tengfei commented Jan 13, 2021

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
Copy link

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

@qzruncode
Copy link

这道题还是非常不错的,可以锻炼正则和错误处理
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
Copy link

fanerge commented May 17, 2021

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
Copy link

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
Copy link

Luoyuda commented Jun 10, 2021

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
Copy link

Evllis commented Aug 11, 2021

/**
 * @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
Copy link

Weibozzz commented Sep 9, 2021

在 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
Copy link

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
Copy link

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
Copy link

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
Copy link

/**
 * 实现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"));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScript teach_tag 滴滴 company
Projects
None yet
Development

No branches or pull requests