最近题主遇到了一个业务需求,需要从多个多层对象结构(即formily的JSON Schema)中查找一个已确定属性名但是不确定层级的一个数组值,由业务端提供属性的层级路径。这个需求跟lodash的get方法不能说毫无关系,只能说是一模一样。
lodash官方解释:
_.get(object, path, [defaultValue])
根据 object对象的path路径获取值。 如果解析 value 是 undefined 会以 defaultValue 取代。
参数
object(Object) : 要检索的对象。path(Array|string) : 要获取属性的路径。[defaultValue]()* : 如果解析值是undefined,这值会被返回。
返回
()* : 返回解析的值。
例子
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'
为了不在已经过于臃肿的依赖结构里添加lodash拖慢上线速度,决定手写该函数在项目中引用。 由于实现逻辑集中于第二个参数,这里我就直接以lodash的get中的第二个参数入手进入思考。 例子中表明第二个参数会有两种形式
- 字符
不做直接处理,转数组 - 数组
对数组进行循环,使用reduce函数
const object = { 'a': [{ 'b': { 'c': 3 } }] };
const myGet = (object, path, defaultValue) => {
if (typeof path === 'string') {
const arrPath = path.split(',').reduce((total, currentValue) => {
if (currentValue.includes('[') && currentValue.includes(']')) {
const startIndex = currentValue.indexOf('[');
const endIndex = currentValue.indexOf(']');
return [
...total,
currentValue.substring(0, startIndex),
currentValue.substring(startIndex + 1, endIndex)
];
}
return [...total, currentValue];
}, []);
return myGet(object, path.split(','))
}
const result = path.reduce((total, currentValue, currentIndex) => {
if (currentIndex <= 0) {
return object[currentValue];
}
if (total === undefined) {
return undefined;
}
return total[currentValue];
});
return result || defaultValue;
}
myGet(object, 'a[0].b.c');
// => 3
myGet(object, ['a', '0', 'b', 'c']);
// => 3
myGet(object, 'a.b.c', 'default');
// => 'default'
写完后我发现还是不太满意,主要在于reduce循环不能停止,即使在判断获取不到值的情况下仍然会继续运行。其次就是遇到[]时需要用循环处理进行判断仍然不是性能的最优解。
针对这两种情况,我打算用for循环代替reduce,用字符串原型上的replace结合正则表达式,同时添加对意外情况的拦截。
const myGet = (object, path, defaultValue) => {
if (!Array.isArray(path) && typeof path !== 'string') {
throw new Error('传入的path参数类型不正确');
}
if (Array.isArray(path)) {
return myGet(object, path.join('.'), defaultValue);
}
const paths = path.replace(/\[(\d+)\]/g, ".$1").split(".");
let result = object;
for (const [index, p] of Object.entries(paths)) {
result = Object(result)[p];
if (
(result === undefined)
|| (Number(index) !== paths.length - 1 && typeof result !== 'object')
) {
return defaultValue;
}
}
return result;
}
这里面有一个小点需要注意,因为习惯问题我在遍历的时候会使用for...of,在同时需要获得key和value值的情况下需要将待处理值转换为类Map结构即[['a', '我是a'], ['b', '我是b']],转换成该结构所使用Object.entries是属于对象原型方法,而在对象中属性名只能为字符串,故Object.entries转换后的数组的索引都会变成字符串,entries内部石油Object.keys上再封装成的,故keys方法同理。