前几天面试的时候,面试官问到了这个问题,感觉自己答的不是特别好,在这里整理一下~
文章篇幅会比较长,但是看完一定会收获满满~希望你坚持看下去呀~
作用:在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
语法:Object.defineProperty(obj, prop, descriptor)
参数:
let person = {}
let personName = 'lihua'
//在person对象上添加属性name,值为personName
Object.defineProperty(person, 'name', {
//但是默认是不可枚举的(for in打印打印不出来),可:enumerable: true
//默认不可以修改,可:wirtable:true
//默认不可以删除,可:configurable:true
get: function () {
return personName
},
set: function (val) {
return name=val
}
})
//当读取person对象的name属性时,触发get方法
console.log(person.name)
//当修改person对象的name属性时,触发set方法
personName = 'liming'
//检查后发现,修改成功了
console.log(person.name)
通过这种方法,我们成功监听了person上的name属性的变化。
上面的使用中,我们只监听了一个属性的变化,但是在实际情况中,我们通常需要一次监听多个属性的变化。
这时我们需要配合Object.keys(obj)进行遍历。这个方法可以返回obj对象身上的所有可枚举属性组成的字符数组。(其实用for in遍历也可以
下面是该API一个简单的使用效果:
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
利用这个API,我们就可以遍历劫持对象的所有属性
但是如果只是上面的思路与该API的简单结合,我们就会发现并达不到效果,下面是我写的一个错误的版本:
Object.keys(person).forEach(function (key) {
Object.defineProperty(person, key, {
enumerable: true,
configurable: true,
// 默认会传入this
get() {
return person[key]
},
set(val) {
console.log(`对person中的${key}属性进行了修改`)
person[key] = val
// 修改之后可以执行渲染操作
}
})
})
console.log(person.age)
看起来感觉上面的代码没有什么错误,但是试着运行一下吧~你会和我一样栈溢出。这是为什么呢?让我们聚焦在get方法里,我们在访问person身上的属性时,就会触发get方法,返回person[key],但是访问person[key]也会触发get方法,导致递归调用,最终栈溢出。
这也引出了我们下面的方法,我们需要设置一个中转Obsever,来让get中return的值并不是直接访问obj[key]。
let person = {
name: '',
age: 0
}
// 实现一个响应式函数
function defineProperty(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`访问了${key}属性`)
return val
},
set(newVal) {
console.log(`${key}属性被修改为${newVal}了`)
val = newVal
}
})
}
// 实现一个遍历函数Observer
function Observer(obj) {
Object.keys(obj).forEach((key) => {
defineProperty(obj, key, obj[key])
})
}
Observer(person)
console.log(person.age)
person.age = 18
console.log(person.age)
那么我们如何解决对象中嵌套一个对对象的情况呢?其实在上述代码的基础上,加上一个递归,就可以轻松实现啦~
我们可以观察到,其实Obsever就是我们想要实现的监听函数,我们预期的目标是:只要把对象传入其中,就可以实现对这个对象的属性监视,即使该对象的属性也是一个对象。
我们在defineProperty()函数中,添加一个递归的情况:
function defineProperty(obj, key, val) {
//如果某对象的属性也是一个对象,递归进入该对象,进行监听
observer(key)
Object.defineProperty(obj, key, {
get() {
console.log(`访问了${key}属性`)
return val
},
set(newVal) {
console.log(`${key}属性被修改为${newVal}了`)
val = newVal
}
})
}
当然啦,我们也要在observer里面加一个递归停止的条件:
function Observer(obj) {
//如果传入的不是一个对象,return
if (typeof obj !== "object" || obj === null) {
return
}
// for (key in obj) {
Object.keys(obj).forEach((key) => {
defineProperty(obj, key, obj[key])
})
// }
}
其实到这里就差不多解决了,但是还有一个小问题,如果用户输入的属性也是一个对象呢?
预知后事如何 且听明天分解~