如何使用defineProperty监听属性变化

469 阅读3分钟

前言

在我们日常开发中经常会用一个对象去定义数据并给它赋值或者更改对象里面的属性值

const obj = {};
obj.name = "jack";
obj.age = 12;

但是如果我想在设置属性值或者获取属性值之前做一些事,比如打印一行日志,该如何实现这个需求呢?

定义

在ES5中对象还有一个方法Object.defineProperty也可以用来定义属性值,并且可以设置属性的描述符。

Object.defineProperty(object,property,descriptor)

这个方法接收三个参数:

  1. 目标对象
  2. 对象的属性名
  3. 对该属性的描述设置

下面我们用一个简单的例子来看下

const student = {};
Object.defineProperty(student, "grade", {
    value: 4,
    writable: true,
    enumerable: true,
    configurable: true
})
student.grade // 4

同样的我们也可以用下面的方法去设置属性

const student = {};
Object.defineProperty(student, "grade", {
    get:() => {
        return 5
    },
    set: (value) => {
        console.log("value udpate:", value)
    },
    enumerable: true,
    configurable: true
})
student.grade // 5

在以上两种设置方法中,第三个参数用到了不同的值,我们来快速地了解一下他们的作用是什么。

描述符

第三个参数描述符descriptor一共有6个可选键,其中getset是存取描述符,valuewritable是数据描述符,而enumerableconfigurable是这两种描述符可以共享的键. 这里需要注意的是数据描述符和存取描述符是不可以同时存在的。

存取描述符

get: 是一个函数,返回值作为对象的属性值,如果没有返回值则是undefined
set: 是一个函数,接收一个参数,该参数就是新设置的属性值。

数据描述符

value: 属性值,就是当前该属性的值
writable: 布尔值,设置当前属性是否可被写入,默认为false, 也就是说如果为false的时候,该属性就不可以再被复写了。

const student = {};
Object.defineProperty(student, "grade", {
    value: 5,
    writable: false
})

console.log(student.grade) // 5
student.grade = 12
console.log(student.grade) // 5

如上所示,尽管设置了grade等于12,但依然没有生效。

共享键

enumerable: 布尔值,设置该属性是否可以被枚举,默认是false
configurable: 布尔值,设置该属性的描述符是否可以被改写或者删除,默认为false

const student = {
name: "Bob"
};
Object.defineProperty(student, "grade", {
    value: 5,
    enumerable: false,
    configurable: false
})

for (let key in student) {
    console.log(student[key]) // Bob
}

Object.defineProperty(student, "grade", {
    value: 5,
    enumerable: true,
}) // Error: TypeError: Cannot redefine property: grade

如上所示在循环中只打印了name属性值,因为grade的可枚举属性设置成了false。而后面再次去设置属性值的时候把enumerable改成了true,结果报错了,那是因为configurable被设置成了false,描述符不可被改变。

思考

大家可能会问,用这样的方法去定义一个属性值岂不是很麻烦,诚然,但是这个方法在有的场景非常适用,比如Vue框架实现的双向数据绑定,就可以用这个方法去监听对象值的变化,在设置或者获取值的时候去插入一些钩子函数,就可以实现数据层和视图层的同步更新。