在对象中获取或设置一个层级很深的属性,通常要先判断是否存在才能进行下一层的遍历,这将使得我们的代码臃肿不堪。
痛点
现有一列同步数据
const obj = {
client: {
user: {
username: "sunzehui",
age: "20"
}
}
}
此时我们访问客户端(client)的用户名(username)数据一般写法:
const username = obj && obj.client && obj.client.user && obj.client.user.username;
每一层都需要判断属性是否存在,不存在(undefined)的话再访问其属性便会报错
TypeError: Cannot read properties of undefined (reading 'xxx')
同样,在设置对象的深层属性的时候,也有这种情况:
const user = obj && obj.client && obj.client.user;
if (user){
user.sex = "man"
}
解决方案
取值器
实现同步变量迭代取值器,一层层取值,取不到的时候返回传入的默认值,未传入即undefined。
Object.prototype.Getter = function (path, defaultValue) {
// 将路径转换成数组便于操作
const keys = path.split(".")
let result = this[keys[0]];
for (let i = 1; i < keys.length; i++) {
// 如果上一次的属性不存在,则返回传入的默认值
if (result === void 0) {
return defaultValue;
// 如果存在,则继续向下寻找
} else {
result = result[keys[i]]
}
}
return result || defaultValue;
}
// 测试
obj.Getter("client.user.username") // sunzehui
obj.Getter("client.user.sex", "man") // man
赋值器
每次迭代时,如不存在该属性,则创建一份对象(可能会覆盖,也可能继续迭代下一层)
Object.prototype.Setter = function (path, value) {
const keys = path.split(".")
let result = this;
for (var i = 0; i < keys.length - 1; i++) {
if (result[keys[i]] === void 0) {
result[keys[i]] = {}
}
// 当第 i 层不是对象的时候,此时不能进行赋值,抛出异常
if (!(result[keys[i]] instanceof Object)) {
throw new Error("can't set property on no-object at " +
keys.slice(0, i + 1).join("."));
}
result = result[keys[i]];
}
return result[keys[i]] = value;
}
// 测试:
obj.Setter("client.user.sex", "man"); // obj.client.user.sex -> "man"
obj.Setter("client.user.sex.type", "human")
// Error: can't set property on no-object at client.user.sex
tip:这里用var是为了 for 循环外面可以用到 i 变量,利用了 var 声明的变量不带块级作用域的特性。
总结
写的很简单,没有实现数组的访问,赋值器那边 instanceof Object 写的也很潦草
其实 lodash 这个库已经实现了 _.get() 和 _.set() 方法