我是天元,立志做
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进行拆分
如图,拆分为一个数组。其中属性key和数字索引可以直接通过递归获得值。而函数执行单独处理即可。
const keyArr = path.split(/\.|\[|\]/).filter(Boolean)
函数调用拆解
处理好基础的属性索引,我们还需要处理函数调用。
函数只需要处理为函数名和参数两个数据。
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如果你喜欢的话,请
点赞,收藏,转发。