一个小算法:实现一个函数,根据path读取object属性及函数执行

640 阅读3分钟

我是天元,立志做1000个有趣的项目的前端,公众号:前端cssandjs

如果你喜欢的话,请点赞,收藏,转发

背景

这是一个朋友遇到的真题,实现一个函数function getx(obj,path)。传入对象及path,并返回结果,并通过如下测试。

getx({a:{b:{c:100}}}, 'a.b.c') /* 100 */
getx({a:{b:{c:100}}}, 'a.b.c.d') /* undefined */
getx({a:{b:[{c:100}]}}, 'a.b[0].c') /* 100 */
getx({a:{b:{c:function(){return {d:100}}}}} , 'a.b.c().d') /* 100 */
getx({a:{b:{c:function(factor){return {d:100 * factor}}}}} , 'a.b.c(10).d') /* 1000 */
getx({a:{b:{c:function(x, y){return {d: x * y}}}}} , 'a.b.c(10, 20, "a").d') /* 200 */

取巧方案

刚看到这个题目的第一眼,我想这不就是new Function吗?直接上代码

const getx =  (obj, path)=> {
  const fun = new Function("obj", `return obj.${path}`);
  return fun(obj);
};

但是这是一个取巧方案,真正面试时肯定不能这么写,所以我们按照正常逻辑再来一遍

path拆解

可以看到path参数的范围分为两种

  • 属性key,字符串类型,如a.b
  • 数字索引,如a[0]
  • 函数无参数调用,如c()
  • 函数带参数调用,如c(10)

根据以上需求,我们可以直接对path进行拆分

image.png转存失败,建议直接上传图片文件

如图,拆分为一个数组。其中属性key和数字索引可以直接通过递归获得值。而函数执行单独处理即可。


    const keyArr = path.split(/\.|\[|\]/).filter(Boolean)
	

函数调用拆解

处理好基础的属性索引,我们还需要处理函数调用。 image.png

函数只需要处理为函数名和参数两个数据。

  if (key.includes("(")) {
      const [funcName, argString] = key.split(/\(|\)/);
      const args = argString?.split(",") || [];
    }

完整函数

需要递归处理属性,我这里用rduce,也可以使用for循环。

function getx(obj, path) {
  const keyArr = path.split(/\.|\[|\]/).filter(Boolean);
  return keyArr.reduce((obj, key) => {
    if (key.includes("(")) {
      const [funcName, argString] = key.split(/\(|\)/);
      const args = argString?.split(",") || [];
      return obj[funcName](...args);
    }

    return obj[key];
  }, obj);
}

至此,之前提到的例子已经全部完成。

新的问题

在之前的例子中,没有对函数调用参数进行判断。 例如:

getx({a:{b:{c:function(x, y){return {d: x * y}}}}} , 'a.b.c(10, 20, "a").d') 

这里的x和y实际上传入的都是字符串,如果我们改成加法,就会出问题。例如

getx({a:{b:{c:function(x, y){return {d: x + y}}}}} , 'a.b.c(10, 20, "a").d') 
// 预期30,实际1020

字符串传入的其实也不对,传入的不是'a',而是'"a"'。这里我们再次处理下,同时加入数字转换,字符串转换,布尔值转换

  let args = argString?.split(",") || [];
      args=args.map(arg=>{
        if(arg==='null') return null;
        if(arg==='undefined') return undefined;
        if(arg==='true') return true;
        if(arg==='false') return false;
        if(arg[0]==='"' && arg[arg.length-1]==='"') return arg.slice(1,-1);
        if(!isNaN(arg)) return +arg;
      });

终极版本

function getx(obj, path) {
  const keyArr = path.split(/\.|\[|\]/).filter(Boolean);
  return keyArr.reduce((obj, key) => {
    if (key.includes("(")) {
      const [funcName, argString] = key.split(/\(|\)/);
      let args = argString?.split(",") || [];
      args=args.map(arg=>{
        if(arg==='null') return null;
        if(arg==='undefined') return undefined;
        if(arg==='true') return true;
        if(arg==='false') return false;
        if(arg[0]==='"' && arg[arg.length-1]==='"') return arg.slice(1,-1);
        if(!isNaN(arg)) return +arg;
      });
      return obj[funcName](...args);
    }

    return obj[key];
  }, obj);
}

我是天元,立志做1000个有趣的项目的前端,公众号:前端cssandjs

如果你喜欢的话,请点赞,收藏,转发