一个对象的属性要么是 数据属性 ,要么是 访问器属性 (accessor properties),属性描述符(descriptor)的不同决定了属性的不同类型。
数据属性的描述符
configurable
若为true
,则该属性的描述符可以被修改/被配置,该属性也可被删除enumerable
若为true
,则该属性可被枚举(可出现在诸如for...in...
Object.keys()
Object.entries()
等操作中)writable
若为true
,则该属性的值可以被修改,即该属性可写-
访问器属性的描述符
configurable
enumerable
get
一个没有参数的函数,在读取属性时自动调用,读取属性的结果为函数的返回值set
一个带有一个参数的函数,在属性被赋值时自动调用,要赋的新值即为函数的参数查看属性的描述符
通过
Object.getOwnPropertyDescriptor(obj, prop)
可以查看对象单个属性的描述符- 通过
Object.getOwnPropertyDescriptors(obj)
可以查看对象所有属性的描述符
类似于: let obj = {name: 'John'}
obj.age = 18
,通过这种常用的方式创建的出来的属性都是数据属性,且描述符 configurable
writable
enumrable
的值都为 true
。
let obj = {name: 'John'}
Object.getOwnPropertyDescriptor(obj, 'name')
// -> {value: 'John', configurable: true, writable: true, enumerable: true}
obj.age = 18
Object.getOwnPropertyDescriptors(obj)
/* ->
{
name: {
value: 'Jhon',
configurable: true,
writable: true,
enumerable: true
},
age: {
value: 18,
configurable: true,
writable: true,
enumerable: true
}
}
*/
定义属性的描述符
- 通过
Object.defineProperty(obj, prop, descriptor)
可以为对象的某个属性设置描述符 - 通过
Object.defineProperties(obj, descriptors)
可以一次为对象设置多个属性描述符
注:若使用以上两种方法定义属性的描述符,那么进行配置的配置项均为 false
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'John',
writable: false // 不可写
})
obj.name = 'Frank' // 写入失败
let descriptor = Object.getOwnPropertyDescriptor(obj, 'name')
descriptor.enumerable // -> false; 没有在 defineProperty() 中指明的配置项,均默认为 false
descriptor.configurable // -> false; 没有在 defineProperty() 中指明的配置项,均默认为 false
delete obj.name // 执行失败,因为 name 属性的描述符 configurable 为 false
Object.defineProperties(obj, {
age: {value: 18; enumerable: true}, // 可枚举
surname: {value: 'Smith', enumerable: false} // 不可枚举
}
Object.keys(obj) // => ['age']
访问器属性由“getter”和“setter”方法表示。在对象字面量中,它们用
get
和set
表示
let user = {
name: "John",
surname: "Smith"
};
Object.defineProperty(user, 'fullName', {
get: function() {
return `${this.name} ${this.surname}`;
}, // 可以简写为 get() {...}
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
user.fullName; // => 'John Smith'
user.fullName = 'Alice Cooper'
Object.values(user) // => ['Alice', 'Cooper']
如果只声明了 get()
方法,那么该访问器属性可读不可写;如果只声明了 set()
方法,那么该访问器属性可写不可读;只有同时声明了 get()
方法和 set()
方法,该属性才可读可写。
访问器属性有别于数据属性,我们可以将其视为一种“虚拟”的数据属性。
let user = {
// 声明数据属性 name surname
name: "John",
surname: "Smith",
// 声明访问器属性 fullName
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
数据属性 name
surname
的读取、写入是直接对该属性操作;而读取访问器属性 fullName
时,是通过执行 get()
方法获得返回值,写入访问器属性时,是通过执行 set()
方法,也正因为如此,我们可以在访问器属性上实现更丰富、更灵活的功能。
// 这样定义访问器属性会导致程序堆栈溢出
let obj = {
get prop() {
return this.prop
},
set prop(val) {
this.prop = val
}
}
// 为访问器属性 prop 引入中间属性 _prop 即可避免堆栈溢出
let obj = {
get prop() {
return this._prop
},
set prop(val) {
this._prop = val
}
}
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("Name is too short, need at least 4 characters");
return;
}
this._name = value;
}
};
这里我们加入了 user._name
将其作为 user.name
的内部属性,多出来这样一个内部属性,可以使我们对 user.name
的操作空间更大,留有的余地更多。
任何时候都不应该在对象外部操作内部属性