Fe-interview: 第 14 题:实现 lodash 的_.get

Created on 19 Jun 2020  ·  7Comments  ·  Source: lgwebdream/FE-Interview

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

JavaScript 滴滴

Most helpful comment

在 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

All 7 comments

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

在 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参数还有数组的可能

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");

function get(target, path, defaultValue) {
  let keys = path.replace(/\[(\d+)\]/g, ".$1").split(".");
  return keys.reduce((t, key) => (t || {})[key], target) ?? defaultValue;
}
这道题还是非常不错的,可以锻炼正则和错误处理
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');
Was this page helpful?
0 / 5 - 0 ratings

Related issues

lgwebdream picture lgwebdream  ·  3Comments

Genzhen picture Genzhen  ·  4Comments

Genzhen picture Genzhen  ·  4Comments

Genzhen picture Genzhen  ·  5Comments

lgwebdream picture lgwebdream  ·  6Comments