持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情
前言
当不确定某个对象上是否存在指定的属性时,每次读取该属性的值时,就需要沿着路径逐层判断。
在如今的 JS 中,可以借助可选链 ?. 进行遍历,十分方便。
当在这之前,开发者如果有导入 lodash 这个工具包,通常会使用它的 get 方法进行访问。
看到某份面经上有手写 get 方法的面试题,于是有了今天这篇文章。
测试数据
测试数据使用文档给出的例子:
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'
代码
首先,定义函数,然后确定参数及其返回值。
参数按照官网有三个,分别是要检索的对象 obj,要获取属性的路径 path,找不到属性的值时的返回值 defaultValue。
至于返回值,可以时任何值,但是有一个默认值,最终定义如下:
/**
* 获取对象执行属性的值
* @param { Object } obj 取值对象
* @param { string | Array<string> } path 键值
* @param { ang } defaultValue 找不到目标属性时的返回值
* @return { any }
*/
function myGet (obj: {}, path: string | Array<string>, defaultValue: any = undefined): any {
return defaultValue
}
接下来就是对参数进行检测,但是因为,path 的值有两种类型,为了方便参数的检测,优先把 path 处理成 Array(方便后续读取属性的值):
// 路径解析,转换成为数组
// 首先进行类型判断,如果时字符串,则处理为数组
if (typeof path === 'string') {
// 正则表达式配合 replace,将 `[` 替换为 `.` ,把 `]` 替换为空字符串
// 如`a[0].b.c[1]` 处理为 `a.0.b.b.1`
path = path.replace(/([\[\]])/g, ($1) => {
return $1 === '[' ? '.' : ''
}).split('.') // 然后按照 `.` 切割为数组
}
然后就是参数检测,一共有四个判断条件:
obj不能为null;obj必须是对象;path必须为数组;path的长度不能为0;- 当
path.length === 0时,说明不读取属性,直接返回obj,其他返回defaultValue; 代码如下:
// 参数检测
if (!obj || typeof obj !== 'object' || !Array.isArray(path) || path.length === 0) {\
// 当 `path.length === 0` 时,说明不读取属性,直接返回 `obj`,其他返回 `defaultValue`
return path.length === 0 ? obj : defaultValue
}
最后就是按照 path 逐层读取属性,如果某一层不存在,则返回 defaultValue。
而按照 path 逐层读取属性,无非就是循环或者递归实现,代码如下:
// 按照路径顺序取值
let target = obj
for (const item of path) {
target = target[item]
// target 为undefined,结束循环
if (!target) break
}
// target 为假值时,返回 defaultValue
return target || defaultValue
完整代码如下:
function myGet (obj: {}, path: string | Array<string>, defaultValue: any = undefined): any {
if (typeof path === 'string') {
path = path.replace(/([\[\]])/g, ($1) => {
return $1 === '[' ? '.' : ''
}).split('.')
}
if (!obj || typeof obj !== 'object' || !Array.isArray(path) || path.length === 0) {
return path.length === 0 ? obj : defaultValue
}
let target = obj
for (const item of path) {
target = target[item]
if (!target) break
}
return target || defaultValue
}
测试一把:
console.log('result 1:', myGet(testObject, 'a[0].b.c'))
// result 1: 3
console.log('result 2:', myGet(testObject, ['a', '0', 'b']))
// result 2: {c: 3}
console.log('result 3:', myGet(testObject, 'a.b.c', null))
// result 3: null
至此,基本完成。