JS对象的数据属性和访问器属性是什么意思

我们知道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: falseconfigurable: false,就可以把"name"属性变成一个常量属性(不可修改、重定义或者删除):

Object.defineProperty(obj, "name", {
    writable: false,
    configurable: false
});

接下来再在obj对象下创建一个新的常量属性"like",把它的属性值设置为"girls",并设置为可枚举:

Object.defineProperty(obj, "like", {
    value: "girls",
    enumerable: true
});

注意!使用Object.defineProperty()方法创建的属性默认是不可配置、不可修改、不可枚举的,因此这里省略了对configurablewritable的设置,使它们保持默认即可。

也可以使用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;
}
}