使用场景
我们对于对象的使用,有以下操作:
创建对象: let obj = {}
给obj对象的name属性赋值: obj.name = 'sanmu'
将obj的name属性赋值给变量name: const name = obj.name
调用obj的某个方法: obj.func()
除了上面的一些操作,我们还可以通过 Object.defineProperty 来定义新的属性或修改原有的属性,并返回此对象。
简单的认为,Object.defineProperty 可以使定义的属性操作更加多样化,而不是仅仅就是获取或者赋予值。
定义
Object.defineProperty(obj, prop, descriptor)
参数
obj: 要定义属性的对象。
prop: 要定义或修改的属性的名称。
descriptor: 要定义或修改的属性描述符。
返回值
被传递给函数的对象。
对于我们上面定义的 obj.name 中,obj 对应的参数 obj,name 对应参数 prop。
至于 descriptor 稍微复杂一些,它有两种形式:数据描述符和存储描述符。
数据描述符: 一个具有值的属性,该值可写可不写
存储描述符: 由 getter 函数和 setter 函数所描述的属性。
descriptor要不是数据描述符,要不就是存储描述符,不能同时是两者。
数据描述符和存储描述符都是对象。
它们都有以下属性:
configurable: 值为boolean。当值为 true 时,obj 的的属性能够被改变,也能够被删除。默认为 false
enumerable: 值为boolean。当值为 true 时,obj 的属性才会出现在对象的枚举属性中(可以被for...in 或者 Object.keys直接获取到的属性)。默认为 false
const obj = {name: 'sanmu', age: 18}
Object.keys(obj)
// 输出name, age,这里的name和age都是可枚举属性。若给age增加 enumerable=false,
// 则age变无法被获取到
数据描述符独有的属性:
value: obj 的属性对应的值。默认 undefined。
writeable: 值为boolean。当值为 true 时,obj 的属性的值就是上面的value。默认为 false
存储描述符独有的属性:
get: 属性的 getter 函数,如果没有 getter,则为 undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined 。
set: 属性的 setter 函数,如果没有 setter,则为 undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this
对象。 默认为 undefined 。
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
所以,我们描述一个对象的属性可以是如下方式
const obj = {name: 'sanmu'}
Object.defineProperty(obj, 'name', {
configurable: true | false,
enumerable: true | false,
value: '任意类型的值',
writable: true | false
})
举例
首先,先创建一个对象
const obj = {}
给 obj 对象增加一个 name 属性,并且给它的 value 赋予 sanmu
const obj1 = {}
Object.defineProperty(obj1, 'name', {
value: 'sanmu'
})
console.log(obj1.name) // 输出sanmu
writable
接着,我们给 writable ,它可以控制属性值是可以被重写。
// writable: false
const obj2 = {}
Object.defineProperty(obj2, 'name', {
value: 'sanmu',
writable: false
})
obj2.name = 'new sanmu' // 将name的属性赋予新value new sanmu
console.log(obj2.name) // 输出还是 sanmu
可以看到,当 writable = false的时候,虽然我们给name=‘new sanmu’,但是实际上并没有被修改。
// writable: true
const obj3 = {}
Object.defineProperty(obj3, 'name', {
value: 'sanmu',
writable: true
})
obj3.name = 'new sanmu' // 将name的属性赋予新value new sanmu
console.log(obj3.name) // 输出 new sanmu
当 writable = true,name被改成了new samu
enumerable
可以控制对象的属性是否能够被枚举。
首先,给 name 属性增加 enumerable = false,我们输出 Object.keys(obj)
const obj4 = {}
Object.defineProperty(obj4,'name', {
enumerable: false
})
console.log(Object.keys(obj4)) // 输出 []
可以看到虽然 name 是 obj 的属性,但是并没有被输出。
const obj5 = {}
Object.defineProperty(obj5,'name', {
enumerable: true
})
console.log(Object.keys(obj5)) // 输出 ['name']
enumerable = true,name 属性就可以被正常输出了
cofigurable
用于控制对象的属性是否可以被删除。若configurable = true,可以被删除;否则不可以。
我们先给 configurable = true,
const obj6 = {}
Object.defineProperty(obj6, 'name', {
configurable: true,
value: 'sanmu'
})
console.log(obj6.name) // 输出 sanmu
delete obj6.name
console.log(obj6.name) // 输出 undefined
再给 configurable = false
const obj7 = {}
Object.defineProperty(obj7, 'name', {
configurable: false,
value: 'sanmu'
})
consolle.log(obj7.name) // 输出 sanmu
delete obj7.name
console.log(obj7.name) // 输出 sanmu
get/set
getter和setter方法用于给对象的属性设置或者获取。
比如我们使用 obj.name = 'sanmu',给name属性赋予值是sanmu,就是触发了set方法。
const name = obj.name就是触发了get方法
const obj8 = {name: 'sanmu'}
let value
Object.defineProperty(obj8,'name',{
get: function() {
return 1
},
set: function(val) {
value = val
}
})
obj8.name = 'new sanmu'
onsole.log(obj8.name) // 输出1
console.log(value) // 'new sanmu'
可以看到当我给 obj8.name = 'new sanmu' 时,触发了 set 方法,给value 赋予 'new sanmu'。
输出 obj8.name 的时候,触发 get 方法,这里return 1,从而我们获取name属性真实的值,被拦截了。
引用: