我们知道JS对象中包含了一组属性,表现形式是属性名:
属性值,每条属性之间用,
号隔开。
比如下面这个对象包含三条属性,一条属性名为"name"
,属性值为"yibo"
;一条属性名为"age"
,属性值为27
;一条属性名为"getAge"
,属性值是一个函数,返回"name"
属性的值。
const obj = {
name: "yibo",
age: 27,
getAge() {
return this.age;
}
}
像这样直接输入“名值对”来创建的对象属性默认是数据属性(数据描述符)。
数据属性的特点是:每一条数据属性都有四个特性,分别是[[Configurable]]
、 [[Enumerable]]
、[[Writable]]
、[[Value]]
,这些被两个中括号括起来的特性是内部特性,因此不能通过.
号或者[]
号直接访问。
[[Configurable]]
定义属性是否可配置(比如是否可以修改它的特性、是否可以删除),接受一个布尔值(true
或者false
),默认值是true
。
[[Enumerable]]
定义属性是否可以枚举(即是否可以通过for-in
循环返回),接受一个布尔值(true
或者false
),默认值是true
。
[[Writable]]
定义属性的值是否可以被修改,接受一个布尔值(true
或者false
),默认值是true
。
[[Value]]
定义属性的值,它等于我们在创建属性时,冒号后面指定的值。
我们可以使用Object.getOwnPropertyDescriptors()
方法查看对象中所有属性的特性(接收一个对象作为参数);
也可以使用Object.getOwnPropertyDescriptor()
来查看单条属性的特性(接收两个参数,第一个参数是对象,第二个参数是属性名)。
比如查看obj
对象下"name"
属性的特性,可以使用Object.getOwnPropertyDescriptor(obj, "name");
命令。
返回值如下:
{value: 'yibo', writable: true, enumerable: true, configurable: true}
返回值包含了"name"
属性的全部特性。
注意!Object.getOwnPropertyDescriptor()
只对自有属性有效,对继承的属性或不存在的属性返回undefined
。
另外,通过操作属性的特性,我们可以更好地控制属性。
操作属性的特性可以使用Object.defineProperty()
方法,这个方法不仅可以修改属性,也可以直接创建属性。
使用Object.defineProperty()
创建属性的好处是可以在创建属性的同时定义它的特性(接收三个参数,第一个参数是要为之添加或修改属性的对象,第二个参数是属性名,第三个参数是一个对象,用于定义属性的特性)。
比如通过把obj
对象下"name"
属性的特性改为writable: false
、configurable: false
,就可以把"name"
属性变成一个常量属性(不可修改、重定义或者删除):
Object.defineProperty(obj, "name", {
writable: false,
configurable: false
});
接下来再在obj
对象下创建一个新的常量属性"like"
,把它的属性值设置为"girls"
,并设置为可枚举:
Object.defineProperty(obj, "like", {
value: "girls",
enumerable: true
});
注意!使用Object.defineProperty()
方法创建的属性默认是不可配置、不可修改、不可枚举的,因此这里省略了对configurable
和writable
的设置,使它们保持默认即可。
也可以使用Object.defineProperties()
方法同时创建多条属性(传入两个参数,第一个参数是要为之添加或修改属性的对象名称,第二个参数是一个对象,用于定义需要添加或修改的属性和与之对应的特性)。
比如继续在obj
对象下创建"love"
和"hate"
属性:
Object.defineProperties(obj, {
love: {
value: "money",
enumerable: true,
configurable: true,
writable: true
},
hate: {
value: "work",
enumerable: true,
configurable: true,
writable: true
}
})
以上就是数据属性的特性以及与之相关的一些常用方法。
接下来我们再来说一说访问器属性:
访问器属性也有四个特性,其中有两个与数据属性相同,分别是[[Configurable]]
和[[Enumerable]]
,另外两个是[[Get]]
和[[Set]]
。
访问器属性不包含数据值,因此不包含[[Value]]
和[[Writable]]
这两个与值有关的特性,但是它包含一个获取(getter
)函数和一个设置(setter
)函数。
[[Get]]
定义获取函数,在读取属性时调用,默认值为undefined
。
[[Set]]
定义设置函数,在写入属性值时调用,默认值为undefined
。
访问器属性的作用是:可以在属性被获取、或者被设置时执行一些操作。
我们可以使用上面讲到的Object.defineProperty()
方法来创建访问器属性。
比如有一个numbers
对象,包含"numberOne"
和"numberTwo"
两个属性。
const numbers = {
numberOne: 1,
numberTwo: 2
}
接下来,我们创建一个访问器属性"numberThree"
,定义获取函数,指定当它被访问时,返回"numberOne"
和"numberTwo"
两个属性值的和:
Object.defineProperty(numbers, "numberThree", {
configurable: true,
enumerable: true,
get() {
return this.numberOne + this.numberTwo;
}
})
这样,当我们使用numbers.numberThree
来访问"numberThree"
属性时,就会返回数值3。
我们有时需要返回动态计算的值,或者需要返回内部变量的值,都可以通过这种方法实现。
接下来,再给这个"numberThree"
属性定义设置函数,指定当它被设置为某个数值时,让"numberOne"
的属性值加上这个值,"numberTwo"
的属性值乘以这个值。
Object.defineProperty(numbers, "numberThree", {
set(x) {
if (isNaN(x)) {
return 'Not a Number!';
}
this.numberOne += x;
this.numberTwo *= x;
}
})
注意!set()
函数接收一个参数,给访问器属性赋值时,这个值会传给这个参数。
完成这一操作后,我们开始给"numberThree"
属性赋值:
numbers.numberThree = 5;
然后我们可以使用console.log(numbers);
查看numbers
对象,就会发现"numberOne"
属性的值变成了6,"numberTwo"
属性的值变成了10,返回值如下所示:
{numberOne: 6, numberTwo: 10}
我们也可以使用上面讲过的Object.getOwnPropertyDescriptors(numbers);
查看numbers
对象中所有属性的完整特性,返回值如下所示:
{
numberOne: {value: 6, writable: true, enumerable: true, configurable: true}, numberTwo: {value: 10, writable: true, enumerable: true, configurable: true},
numberThree: {enumerable: true, configurable: true, get: ƒ, set: ƒ}
}
"numberOne"
和"numberTwo"
是数据属性,"numberThree"
属性是访问器属性。
怎么样?经过上面的讲解,属性类型是不是一目了然?
这时,当我们再用numbers.numberThree
访问"numberThree"
属性时,就会发现返回值变成了16。
访问器属性也可以使用对象字面量的方式定义,同样是numbers
对象,我们也可以这样创建:
const numbers = {
numberOne: 1,
numberTwo: 2,
get numberThree() {
return this.numberOne + this.numberTwo;
},
set numberThree(x) {
if (isNaN(x)) {
return 'Not a Number!';
}
this.numberOne += x;
this.numberTwo *= x;
}
}