简介
Object.definePropety() 方法可以直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回此对象。
描述
Object.defintPropety(data, key, descriptor)
该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for…in 或 Object.keys 方法),可以改变这些属性的值,也可以删除这些属性。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。
参数
- data 须要定义属性的对象
- key 需被定义或修改的属性名
- descriptor 要定义或修改的属性的描述符
descriptor 详细参数
configurable 该属性的 configurable 为 true 时,该属性才可以被修改或能够经过 delete操作符删除该属性,默认为 false (将属性设置为只读,不可修改也不可删除)
enumerable 该属性的 enumerable 为 true 时,该属性才可以出如今对象的枚举属性中,默认false
value 该属性对应的属性值,可以为任何有效的 JS 值,默认为 undefined
writable 当该属性的 writable 为true时,该属性才能被赋值运算改变,默认为false(将属性设置为不可枚举)
get 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。(简单的说就是,当没有设置get 属性的时候,访问属性会默认返回属性值,配置这个函数的时候可以监听到访问的属性以及属性值,可以支持处理不同的操作,比如说给书籍增加《》。)
set 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值)。(配置这个函数的时候,可以监听到属性的修改,以及修改的新值)
使用示例
若是当前对象不存在要设置的属性,Object.defineProperty() 会根据方法设置为对象创建一个新的属性
let data = {} // 创建一个空对象
let age = 18
Object.defineProperty(data, 'age', {
// value: age,
// configurable: false, // --> 是否可以通过 delete 操作符删除该属性
// enumerable: false, // --> 循环的时候, 是否可以被枚举出来
// writable: false, // --> 是否可以使用 ++, -- 等操作符
get() { // --> 当属性被访问的时候触发, 这个方法里需要返回一个值, 否则访问的时候会报错
console.log(`访问了age`);
return age // 返回一个值
},
set(newValue) { // --> 当属性被修改的时候出发, 需要给原来的属性赋值为新值, 否则对象中保存的值还是旧的值
console.log(`触发了修改age, 新值为${newValue}`);
age = newValue // 修改
}
})
setTimeout(() => {
console.log(data); // 访问对象
console.log('添加绑定后的值为', data.age); // 查看属性绑定是否会触发 getters 的监听
data.age = 30 // 修改值后查看, 属性是否触发 Setters 的监听
console.log('修改后的值为', data.age); // 查看修改后的值
}, 200)
代码输出结果
这里我们可以看到添加的双向绑定已经生效了, 当我们访问,修改 age 属性的时候,都会触发get,set 监听,我们可以在这些监听里面做相应的操作。
封装 Object.defineProperty
因为原生的 Object.defineProperty 并不是很好用,所以我们在使用到它的地方,进行二次封装让它更好用一些。
function definedReactive(data, key, value){
if (!value) {
value = data[key]
}
Object.defineProperty(data, key, {
// ... 这里写个性化配置
})
}
这样的话,调用的时候就只需要调用 definedReactive 方法传入对应的值就可以让对象变成相应式的数据了。
拓展
这个 JS 的原生属性对数组的支持并不是很友好,监听不到数组下标变化、数组的 push pop shift slice splice 等方法对原数组的修改,及对象新增属性,因为监听的不是对象本身,而是对象属性。
同时性能和可扩展性上面也不如 ES6(ESMAScript2015)中提出的 proxy 支持度更好一些。
在 Vue3 中使用 Proxy 代替 Object.defineProperty 重构了响应式系统,可以监听到数组下标变化,及对象新增属性,因为监听的不是对象属性,而是对象本身,还可拦截 apply、has 等 13 种方法