主题
对象属性与描述符
平时写对象属性时,最常看到的是“键值对”。但在 JavaScript 底层,一个属性除了值本身,还带着一组描述信息。
属性不只有 value
最常见的数据属性,通常还带有三个标志:
writable:能不能重新赋值enumerable:会不会出现在遍历结果里configurable:能不能继续修改描述符本身,或删除这个属性
查看属性描述符
可以用 Object.getOwnPropertyDescriptor() 查看一个属性的完整描述:
js
const user = {
name: 'Lee',
}
console.log(Object.getOwnPropertyDescriptor(user, 'name'))普通直接声明出来的属性,默认通常是:
writable: trueenumerable: trueconfigurable: true
使用 defineProperty
如果想精确控制属性行为,通常使用 Object.defineProperty():
js
const user = {}
Object.defineProperty(user, 'name', {
value: 'Lee',
writable: false,
enumerable: true,
configurable: true,
})这时 name 就不再是一个普通可写属性了。
writable
writable: false 表示这个属性不能被重新赋值:
js
const user = {}
Object.defineProperty(user, 'name', {
value: 'Lee',
writable: false,
})
user.name = 'Fan'在严格模式下,这类写入会报错;在非严格模式下通常会静默失败。
enumerable
enumerable: false 表示这个属性不会出现在枚举结果里:
js
const user = {
name: 'Lee',
toString() {
return this.name
},
}
Object.defineProperty(user, 'toString', {
enumerable: false,
})
console.log(Object.keys(user)) // ['name']这会影响:
for...inObject.keys()Object.entries()
configurable
configurable: false 表示这个属性不能再被删除,也不能继续改描述符的大部分配置。
js
const user = {}
Object.defineProperty(user, 'name', {
value: 'Lee',
configurable: false,
})
delete user.name // false 或报错可以把它理解成“这个属性的结构已经基本锁死”。
数据属性和访问器属性
对象属性大致可以分成两类:
- 数据属性:有
value - 访问器属性:有
get/set
这两类描述符不是简单叠加关系。
getter 和 setter
访问器属性看起来像普通属性,但读写时实际会触发函数:
js
const user = {
name: 'Lee',
surname: 'Fan',
get fullName() {
return `${this.name} ${this.surname}`
},
set fullName(value) {
;[this.name, this.surname] = value.split(' ')
},
}
console.log(user.fullName)
user.fullName = 'V Fan'这类属性很适合:
- 派生值
- 读写校验
- 对外暴露统一接口
访问器描述符
访问器属性没有:
valuewritable
它有的是:
getsetenumerableconfigurable
也可以通过 defineProperty() 创建:
js
const user = {
name: 'Lee',
surname: 'Fan',
}
Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`
},
set(value) {
;[this.name, this.surname] = value.split(' ')
},
enumerable: true,
configurable: true,
})一个常见用法:只读派生属性
js
function User(name, birthday) {
this.name = name
this.birthday = birthday
Object.defineProperty(this, 'age', {
get() {
const currentYear = new Date().getFullYear()
return currentYear - this.birthday.getFullYear()
},
})
}这里的 age 不是直接存储的值,而是读取时即时计算的结果。
和普通赋值的区别
直接写:
js
user.name = 'Lee'更适合日常普通属性。
而 defineProperty() 更适合:
- 限制写入
- 控制枚举行为
- 暴露访问器
- 做底层封装
使用边界
属性描述符很强,但不是所有属性都需要这么设计。
如果只是普通业务字段,直接赋值通常最清晰。
只有在确实要控制属性行为时,再考虑:
- 不可写
- 不可枚举
- 不可配置
- getter / setter
使用建议
- 日常属性优先用普通对象写法。
- 需要控制属性行为时,再想到
Object.defineProperty()。 - 区分数据属性和访问器属性,不要把
value和get/set混在一起理解。 - 写 getter / setter 时,先确认这是不是“属性语义”,而不是普通方法更合适。
