JavaScript面向对象详解之属性描述符
在JavaScript中,我们通常使用对象来组织代码和数据。对象具有属性(Property)和方法(Method),其描述了对象的属性特征和对外提供的行为。属性描述符(Property Descriptor)是对象属性的一种记录类型,用于控制属性的行为,如可写、可读、可枚举、可配置等。
1. 属性描述符的概念与接口
属性描述符是一个完整的数据结构,具有以下四个字段:
- value:属性的值,可将其看作是属性的默认值,若访问该属性,则使用该值;
- writable:属性是否可写入;
- enumerable:属性是否可枚举,如果为 true,该属性会出现在对象的遍历结果中;
- configurable:属性是否可删除,是否可以更改属性的特征值。
针对这些特性,ECMA262定义了一个标准的接口:PropertyDescriptor。
interface PropertyDescriptor {
value?: any;
writable?: boolean;
enumerable?: boolean;
configurable?: boolean;
get?: () => any;
set?: (v: any) => void;
}
由于PropertyDescriptor是一个接口,所以它是由多个实现,它可以被用来描述一个对象的所有的属性值特征或值特征以及存取器特征,这使得JavaScript对象至少支持两种类型的属性:数据属性(Data Property)和存取器属性(Accessor Property)。
数据属性
数据属性是与传统表格相似的键值对,其中value对应的是键名所关联的值,还有一些控制着属性的特性,如上所述。
const obj = {
x: 1,
y: 2
};
const descriptor = Object.getOwnPropertyDescriptor(obj, 'x');
console.log(descriptor.value); // 1
console.log(descriptor.writable); // true
console.log(descriptor.enumerable); // true
console.log(descriptor.configurable); // true
其中PropertyDescriptor使用Object.defineProperty方法来添加或修改属性描述符。
存取器属性
存储器属性是用getter和setter函数拦截对属性值的读写操作,这些函数与数据属性所代表的属性值是独立的。实现了get和set的属性称为“存取器属性”。
let p = {
_name: 'Tom',
get name() {
return this._name;
},
set name(value) {
this._name = value;
}
};
Object.defineProperty(obj, 'name', {
get: function () {
return this._name;
},
set: function (value) {
this._name = value;
},
enumerable: true,
configurable: true
});
2. Property Descriptor内部工作机制
在JavaScript中,每个属性都有一个与之对应的属性描述符(PropertyDescriptor)。属性描述符可以由Object.getOwnPropertyDescriptor、Object.defineProperty、Object.defineProperties生成和修改。
当我们定义一个新的对象时,出现以下代码:
let a = {};
a.x = 1;
当我们给a添加属性x时,实际上是在a对象的内部表中找到名为“x”的槽,如果这个槽不存在,就添加新的槽,并将值设为1。如果这个槽已经存在,值就被覆盖。
每个JavaScript对象中都有一个内部表,用于存放对象的属性值,存放的位置和命名规则与具体实现有关,我们在这里称它为对象的“属性表”。
对于属性描述符来说,与内部表相关的是值(value)、可写、可枚举、可删除或存取器属性的get/set方法。这些信息保存在PropertyDescriptor中,并对应着对象属性表中相应的标记,描述了对对象属性的操作方法。
属性的可写性
当writable为true时,表示属性可写,否则为不可写。不可写的属性值不能被修改,如果试图改变,JS引擎会忽略修改请求,并且不会显示给外部代码任何错误或异常信息。为了检测属性描述符对象中的writable属性的值,我们可以直接使用Object.getOwnPropertyDescriptor方法。
var obj = {};
Object.defineProperty(obj, 'a', {
value: 37,
writable: false
});
console.log(Object.getOwnPropertyDescriptor(obj, 'a').writable); //output false
可枚举属性
当enumerable为true时,表示属性可枚举,否则为不可枚举。
所有挂载到对象上的自有属性(默认情况下)都是可枚举的,可枚举代表该对象的属性是否可在循环中被访问。例如,使用for...in、Object.keys、JSON.stringify等内置函数时,都会依赖该属性。
var obj = {};
Object.defineProperty(obj, 'a', {
value: 37,
writable: false,
enumerable: true
});
console.log(Object.getOwnPropertyDescriptor(obj, 'a').enumerable); //output true
可删除性
当configurable为true时,表示该属性可删除,否则表示不可删除。
当configurable为false时,意味着该属性的描述符被锁定,无法再被修改。当然,即使该属性的值descriptor.configurable是true,也不一定意味着它会被成功地删除,这取决于属性是否需要被删除,如果是自带的内置属性或不允许删除的属性,我们尝试删除则会报错。
var obj = {};
Object.defineProperty(obj, 'a', {
value: 37,
writable: false,
enumerable: true,
configurable: false
});
console.log(Object.getOwnPropertyDescriptor(obj, 'a').configurable); //output false
总结
属性描述符是JavaScript对象属性的一种记录类型,用于控制属性的行为,如可写、可读、可枚举、可配置等。属性描述符可以由Object.getOwnPropertyDescriptor、Object.defineProperty、Object.defineProperties生成和修改。
在建立对象的属性上,除了value值,我们可以使用标准的描述符对象来定义各种其他的属性行为。对于这些属性行为的设置,我们可以使用Object.defineProperty来进行操作,从而达到精细控制的目的。