0%

面向对象一之理解对象

前言

创建一个对象有两种方式:使用构造函数,或使用对象字面量。实际上在使用对象字面量创建一个对象时不会用到构造函数。对象都有属性和方法,用来描述这个对象。而描述属性的各种特征的“属性中的属性”我们称之为“特性”。


正文

属性类型

属性的特性是用来实现Javascript引擎用的,不能直接使用。我们把特性用两个方括号[[]]包起来。Javascript中有两种属性:数据属性和访问器属性。

数据属性

数据属性保存了一个数据值的位置,可以读取和写入。它有4个特性:

  • [[configurable]]:表示这个属性能否被delete操作符删除,以及能否修改这个属性的其它特性,相当于一个“总开关”。需要注意,一旦这个特性的值设置为false,就不能再设置为true了。
  • [[ennumerable]]:表示这个属性能否被for-in循环枚举。
  • [[writable]]:表示这个属性的值能否被改写。
  • [[value]]:这个位置储存了这个属性的值,读取和写入都在这个位置进行。默认值为undefined.

注意,一个对象的某个属性如果是用文章开头所说的两种方式(构造函数和字面量)定义的,则这个属性的前三个特性的值都为true,最后一个特性为所定义的值。要访问和改写一个属性的特性,需要用到Object.defineProperty(obj,propertyName,descriptor)方法。参数解释如下:

  • obj:需要访问或修改属性的特性的对象。
  • propertyName:需要访问或修改其特性的属性名,注意是字符串。
  • descriptor:一个描述符对象,这个对象的属性就是上述的4个特性中的某些或全部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var person = {name : 'nikolaus'};
delete person.name;
console.log(person.name); //undefined

Object.defineProperty(person,'name',{
configurabel : false,
value : 'liuyu'
});
delete person.name;
console.log(person.name); //'liuyu' delete操作符被忽略

Object.defineProperty(person,'age',{
enumerable : true //没指定configurable,configurable会被设置为false,这导致下面的其他特性改写都无法完成
});
Object.defineProperty(person,'age',{
writable : true //这一步改不了
});
Object.defineProperty(person,'age',{
value : 25 //这一步改不了
});
console.log(person.age); //报错,不能重写

for(var prop in person){
console.log(prop + ':' + person[prop]); //age:undefined
}

在调用Object.defineProperty()方法时,如果不为前3个特性指定值,则其值会被设置为false。从上面的例子也可以看出。

访问器属性

访问器属性不包含数据值(这里不包含数据值是什么意思下面再讲),它总是跟对象的”私有属性”关联,用来读取和写入这些”私有属性”。访问器属性必须通过Object.defineProperty()方法来定义。它也有4个特性:

  • [[configurable]]:表示这个属性能否被delete操作符删除,以及能否修改这个属性的其它特性,相当于一个“总开关”。需要注意,一旦这个特性的值设置为false,就不能再设置为true了。
  • [[ennumerable]]:表示这个属性能否被for-in循环枚举。
  • [[Get]]:读取属性时调用的函数。这个函数不是必须的。
  • [[Set]]:写入属性时调用的函数。这个函数不是必须的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var person = {
_name : 'nikolaus',
_age : 23
};
Object.defineProperty(person,'name',{
get : function(){
return this._name;
}
});
Object.defineProperty(person,'age',{
get : function(){
return this._age;
},
set : function(newValue){
this._age = newValue
}
});
console.log(person.name); //nikolaus
person.name = 'liuyu';
console.log(person.name); //nikolaus
console.log(person.age); //23
person.age = 25;
console.log(person.age); //25

上面的实例定义了一个对象,并为对象添加了两个”数据属性”,并用Object.defineProperty()方法定义了两个访问器属性nameage。其中name属性只为其定义了get函数,age属性则为其定义了get和set函数。我之前一直不理解“访问器属性不包含数据值”这句话是什么意思,上面的实例中person.age = 25;不是一个赋值操作嘛?实际上,可以发现:nameage这两个属性都是跟_name_age关联的,可以理解为这两个访问器属性是为这两个”数据属性”服务的。看起来像是赋值操作的那个语句,实际上是通过get函数把这个值保存在了_age这个属性上,age这个访问器属性上并不保存数据,它只是一个中间的桥梁,通过函数执行数据的读取和写入操作。

通过上面的实例发现:对于一个访问器属性,如果只给他设置get函数,则只能读取对应的属性,而不能执行写入操作;如果只给他设置set函数,则只能写入对应的属性,不能读取。

定义多个属性

Object.defineProperties(obj,descriptor)方法可以为对象一次定义多个属性。第一个参数为将要为其定义属性的对象,第二个参数是一个对象,这个对象的属性就是将要定义的属性,这个对象的属性的值实际上是一个个描述符对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var book = {};
Object.defineProperties(book,{
_year : {
value : 2017,
writable : true
},
edition : {
value : 1,
writable : true
},
year : {
get : function(){
return this._year;
},
set : function(newValue){
if(newValue > 2017){
this._year = newValue;
this.edition += newValue - 2017;
}
}
}
});
book.year = 2018;
console.log(book.edition); //2
console.log(book._year); //2018

读取属性的特性

前面两部分说的都是设置属性的特性,如果要读取属性的特性,则需要用到Object.getOwnPropertyDescriptor(obj,propertyName)方法,返回值是一个对象。如果要读取其特性的属性是一个数据属性,则这个返回的对象的属性为:configurableenumerablewritablevalue;如果要读取其特性的属性是一个访问器属性,则这个返回的对象的属性为:configurableenumerablesetget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var book = {};
Object.defineProperties(book,{
_year : {
value : 2017,
writable : true
},
edition : {
value : 1,
writable : true
},
year : {
get : function(){
return this._year;
},
set : function(newValue){
if(newValue > 2017){
this._year = newValue;
this.edition += newValue - 2017;
}
}
}
});
var des1 = Object.getOwnPropertyDescriptor(book,'_year');
console.log(des1.value); //2017
console.log(des1.configurable); //false
console.log(typeof des1.get); //"undefined"
var des2 = Object.getOwnPropertyDescriptor(book,'year');
console.log(des1.value); //undefined
console.log(des1.configurable); //false
console.log(typeof des1.get); //"function"