引言
每个对象都有自己的属性,这些对象的属性描述了对象的信息和特征。而这些对象的属性也有自己的属性,即对象属性的属性(或称为特征),他们描述了对象中某个属性的特征,例如一个属性是否可以枚举、是否可以修改、是否可以删除等等。
注意:为便于区分,以下部分将对象的属性简称为属性,将对象属性的属性简称为属性的特征。
1 属性的类型
属性有两种类型:
-
数据属性:保存了值,是最常用的属性类型,例如下面 person 对象的 name 属性就是一个数据属性:
let person = { name: 'Yuri' };
-
访问器属性:是个函数,决定了一个属性被读取或写入的方式,函数名就是要读取或者写入的属性名,读取这个属性的函数用
get
后跟属性名来定义,写入一个属性的函数用set
后跟属性名来定义。举个例子,假设访问器属性名为 age, 则定义的语法为:/* 假设访问器属性名为 age, 则定义的语法为: */ let person = { xxx: 99, // 定义读取的函数 get age(){ // do something; return this.xxx; // xxx 将在 age 被读取时返回 }, // 定义写入的函数 set age(value){ this.xxx = value; // 实际上是将 xxx 的属性值设置成 value } }
这种方式从外部看来是在访问
age
,其实是在访问xxx
,实际上,通过get
和set
我们可以自由的处理外界的读取与写入请求,可以返回一个真的值,也可以给外部一个假的值。
这两类属性定义的方式不同,但是访问的方式却是一样的,都可以通过点号访问或者中括号来访问,例如:
// 读取 name 值
console.log(person.name); // Yuri
// 写入 name 值
person.name = 'new name';
console.log('修改后的 name 属性值是 ' + person.name); // 修改后的 name 属性值是 new name
// 读取 age 值
console.log(person.age); // 99
// 写入 age 值
person.age = 1;
console.log('修改后的 age 属性值是 ' + person.age); // 修改后的 age 属性值是 1
1.1 两种属性的关系
虽然两种属性都可以设置和读取其中保存的值,但在作用上还是有区别的。
如果只需要保存和读取数据,则用数据属性来做就够了,没有必要使用访问器属性;但是如果需要修改读取或者写入的默认行为时,则应该将属性用访问器的方式来定义成一个访问器属性~~,然后就可以为所欲为了~~。
在定义访问器属性时,不一定非要同时定义set和get
。当只定义get
时,则属性就成了只读属性,不能写入;如果只定义了set
,则属性就只能写入,不能读取。
访问器属性的优先级较高。当将一个属性名同时由数据属性和访问器属性定义时,访问器属性生效,数据属性被忽略:
let person = {
name: 'Yuri', // 将 name 设置为数据属性
xxx: 99,
get name(){ // 将 name 设置为访问器属性
// do something;
return this.xxx;
},
// 定义写入的函数
set name(value){ // 将 name 设置为访问器属性
this.xxx = '这是通过访问器属性设置的值: ' + value;
}
}
// 读取 name 值
console.log(person.name); // 99
// 写入 name 值
person.name = 'new name';
console.log(person.name); // 这是通过访问器属性设置的值: new name
2 属性的属性(特征)
如引言中所说,对象的属性不仅有值(value),而且还有描述这个属性的特征,例如可读性、可配置性,这些特征是用一个对象来保存的,这个对象称为属性的描述对象,所有的属性都有一个描述对象来描述它的特征。
属性的描述对象在属性被创建的时候就已经生成了,JavaScript会自动创建它,并为它指定默认值。当然,我们也可以在创建一个属性的时候就手动指定这个属性的特征,这个方法在2.2节中进行了讨论。
对于上文提到的两种属性(数据属性和访问器属性)有两个共同的特征,但是由于功能和特点的不同,它们各自也有自己独有的特征,下面分别来讨论。
我们可以手动的去修改这些特征,例如将一个属性的设置为不可枚举的,则用for in
方法或者Object.keys()
方法都不会取得这个属性。
修改属性的特征的方法是Object.defineProperty(obj, propertyName, descriptor);
,其中参数定义如下:
obj
:属性所在的对象propertyName
:属性的名称,是个字符串descriper
:描述属性特征的对象,通过修改这个对象来修改属性的特征
上面的Object.defineProperty()
函数一次只能修改一个属性的特征,如果想一次性同时改变多个属性的特征,需要使用Object.defineProperties(obj, descriptor)
函数,其中的第一个参数也是要被修改属性的对象,第二个参数也是一个特征描述对象,不过在这个对象里可以同时修改多个属性的特征。
下面的内容会使用上面提到的两个方法来改变属性的特征,通过阅读下面的内容,可以同时学会属性每个特征的含义和这两个函数的用法。
2.1 通用的特征
所谓通用的特征是指数据属性和访问器属性都有的特征。这类特征有两个:
-
enumerable
:这个特征描述了属性是否是可以被枚举的,它取布尔值,若为true
,则可以枚举,否则不可被枚举。不可枚举的属性不能被for in
方法或者Object.keys()
方法得到。我们自己定义的每个属性的enumerable
特征的默认值都是true
,即都是可枚举的。let obj = { name: 'Yuri' }; // 默认 name 属性是可枚举的, 可以通过 Object.keys 方法得到 name 这个属性名 console.log(Object.keys(obj)); // ["name"] Object.defineProperty(obj, 'name', { enumerable: false // 设置为不可枚举 }); // 此时 Object.keys 得到的结果中就没有 name 属性了 console.log(Object.keys(obj)); // [] 空数组
-
configurable
:描述这个属性是否可以被配置,取布尔值。是否可以被配置的意思是这个属性的descriptor
描述对象是否能被修改,同时也确定了这个属性是否能被删除。我们自己定义的每个属性的configurable
特征的默认值都是true
,即都是可修改和删除的。首先在默认情况下尝试删除一个对象的属性:
let obj = { name: 'Yuri' }; // 尝试删除 name 这个属性,成功 console.log(obj.name); // Yuri delete obj.name; console.log(obj.name); // undefined,说明 obj 对象已经没有了 name 属性
可以看到在默认情况下,我们自己给对象加的属性是可以被删除的,因为这个属性的
configurable
特征取值为true
。下面将
configurable
属性设置为false
,再尝试删除操作,发现删除失败:Object.defineProperty(obj, 'name', { configurable: false // 设置为不可配置 }); delete obj.name; console.log(obj.name); // Yuri,说明 obj 对象仍有 name 属性
2.2 数据属性特有的特征
数据属性有两个访问器属性所没有的特征:
-
writable
:定义了属性值能否被修改,去布尔值,若为true
则可被修改,否则不能被修改。默认值为true
。let obj = { name: 'Yuri' }; console.log(obj.name); // Yuri obj.name = 'Yuri`s Revenge'; console.log(obj.name); // Yuri`s Revenge, 修改成功,因为 writable 默认为 true Object.defineProperty(obj, 'name', { writable: false // 设置为禁止修改 }); obj.name = 'Red alert'; console.log(obj.name); // Yuri`s Revenge, 依然是修改之前的值,修改失败
-
value
:它保存了属性的值,当我们想取出属性值时,其实取出的就是这个value
所保存的值。例如当我们使用obj.name
来获得name
属性的值时,得到的就是value
的值:
let obj = {
name: 'Yuri'
};
console.log(obj.name); // Yuri
Object.defineProperty(obj, 'name', {
value: '这是通过 value 特征修改后的值'
});
console.log(obj.name); // 这是通过 value 特征修改后的值
通过给属性的value
特征赋值,甚至可以给对象创建一个本来没有的属性并赋上值:
// 创建一个没有 age 属性的对象
let obj = {
name: 'Yuri'
};
console.log(obj.age); // undefined, 因为没有 age 这个属性,所以返回 undefined
// 使用 Object.defineProperty 给对象设置 age 属性的值
Object.defineProperty(obj, 'age', {
value: 99
});
console.log(obj.age); // 99 已成功 age 属性,并赋了值
实际上,上面代码的执行步骤为:先观察对象有没有 age 属性,发现没有,则创建一个,然后将value
值赋了上去。
***注意:***在用这种方法创建对象时,要记得不能只设置value
属性,还要显式的设置其他三个属性(enumerable、configurable、writable)的值,否则这三个特征的值都会被默认设置为false
,即不可遍历、不可配置、不可写。这是和其他创建属性的方式所不同的。
2.3 访问器属性特有的特征
访问器属性也有两个特有的特征,由于访问器属性不需要存储值,所以没有writable和value特征,其特有的特征是:get
和set
特征。
这似乎和第 1 节中讨论的访问器属性重复了,因为访问器属性也是用get和set来定义的数据访问方式。但是他们的不同点在于访问器属性只能在创建对象时定义,而使用get
和set
特征可以随时改变属性,这样就不用再去创建一个新的对象了。
例如改变一个已经定义了访问器属性的get和set特征:
let person = {
_age: 99,
get age(){
return this._age;
},
set age(value){
this._age = value;
}
};
Object.defineProperty(person, 'age', {
get(){ // 由于已经指定了要设置的属性名,所以不必向第1小节一样 get age(){...}
return this._age * 2;
},
set(value){ // 由于已经指定了要设置的属性名,所以不必向第1小节一样 set age(){...}
this._age = 0; // 无论外界想将 name 设置成什么 value, 都忽略, 任性的设置成 0
}
});
console.log(person.age); // 198, 就是 99 * 2
person.age = 18;
console.log(person.age); // 0
注意:当仅设置了get 时,该属性为只读;当仅设置了set时,该属性为仅可写。
2.4 使用 Object.defineProperties() 一次性设置多个属性的特征
Object.defineProperties(object, descriptors)
方法的重点在于对特征描述对象的定义,在第二个参数descriptors
中可以给多个属性定义各自的描述对象,属性和描述对象是以键值对的形式出现的,除此之外,和Object.defineProperty
的效果没有区别。例子如下:
let game = {
name: 'Yuri`s Revenge'
};
console.log(game.name); // Yuri`s Revenge
Object.defineProperties(game, {
name: { // 给 name 属性修改特征描述对象
value: 'Red Alert',
configurable: false,
writable: false
},
creater: { // 创建一个新的属性,并只提供 get 方法,则 creater 属性是只读的
get(){
return 'West Wood'
},
configurable: false,
enumerable: true
}
});
console.log(game.name); // Red Alert
game.name = 'Command & Conquer';
console.log(game.name); // Red Alert
console.log(game.creater); // West Wood
game.creater = 'EA';
console.log(game.creater); // West Wood
3 获取属性的特征描述对象
如果想知道一个属性现在的特征是什么,则可以调用Object.defineProperties(object, propertyName)
,其中第一个参数 object
是属性所在的对象,第二个参数propertyName
是属性名。返回结果是这个属性的特征描述对象。例如获取上个例子中的game
对象的 name
和 creater
属性的特征描述对象:
console.log(Object.getOwnPropertyDescriptor(game, 'name'));
// {value: "Red Alert", writable: false, enumerable: true, configurable: false}
console.log(Object.getOwnPropertyDescriptor(game, 'creater'));
// {get: ƒ, set: undefined, enumerable: true, configurable: false}
// 其中 get: f 中的 f 是 function 的缩写,代表函数
总结
对象有两种属性:数据属性和访问器属性。
对象的属性除了有属性值以外,也有自己的特征,比如是否可遍历、是否可配置等。
数据属性和访问器属性都有4个特征,除了2个共同的特征之外,也各自拥有2个自己独特的特征。可以用图来总结一下:![](D:\NOTES IN GIT\blogs\属性特征.png)