Property-getters-and-setters(译)

  • 白小霁
  • 18 Minutes
  • June 24, 2017

译者:@baixiaoji 原文

属性的getterssetters

这是两种属不同类型的属性。

第一种就是数据的属性。我们已经知道他们是怎样运作的了。实际上,到目前为止我们用过的所有属性都是数据属性。

第二种的属性是有些不同的地方。他是访问器属性(accessor properties)。根本上,他们有设置和获取值的功能,但是从外部代码来看其实和寻常的属性没什么不同。

Getters and setters

访问器属性在代码中的表现形式就是gettersetter这两个方法。在一个字面量声明的对象,这两个方法的是用getset表示的。

1
2
3
4
5
6
7
8
9
let obj = {
get propName() {
// getter, the code executed on getting obj.propName
},
set propName(value) {
// setter, the code executed on setting obj.propName = value
}
};

在读取obj.propName值的时候,getters运行了,而给obj.propName赋值的时候,setter运行了。
举个例子,现在我们有一个user的对象,有namesurname属性。

1
2
3
4
let user = {
name : "John",
surname: "Smith"
};

现在我们希望有一个fullName属性,其返回的值应该是「John Smith」。当然,我们不想复制粘贴现有的信息,所以我们可以将其作为一个访问器。

1
2
3
4
5
6
7
8
let user = {
name : "John",
surname: "Smith",
get fullName(){
return `${this.name} ${this.surname}`;
}
}
alert(user.fullname) // John Smith

从调用的角度来看,一个访问器属性和平常的属性很像。这就是访问器属性的理念。我们不会把user.fullName叫做一个函数,我们将看作一个平常的属性:getter在该场景背后默默运行。

目前为止,fullName只有一个getter。如果我们想去将其赋值时,就像这样user.fullName=,将会有一个报错。
我们通过给user.fullName添加一个setter来修复这个错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let user = {
name : "John",
surname: "Smith",
get fullName(){
return `${this.name} ${this.surname}`;
},
set fullName(value){
[this.name, this.surname] = value.split(" ");
}
}
user.fullName = "Alice Cooper"
alert(user.name) // Alice
alert(usrer.surname) // Cooper

现在我们有一个「虚拟」属性,是一个可读可写的属性,可实际上不存在的属性。

Accessor properties are only accessible with get/set
一个属性可以是数据属性,也可以是一个访问器属性,但不能同时是这两种属性的结合。
一旦将一个属性定义为get prop或是set prop,这就是个访问器属性。这样一来,如果想要读这个属性就要有一个geeter,想要给该属性赋值就要有一个setter。
有时候,可能一个属性只有setter或只有getter。这种情况下,属性只能写或是只能读。

Accessor descriptors(访问器描述符)

与数据属性相比较,访问器的描述符有点不同。

对访问器属性而言,没有valuewritable,将这两个属性代替的是getset方法。
那么一个访问器属性的描述符就有一下四种:

举个例子,通过defineProperty创建一个访问器属性fullName,同时设置getset这两个描述符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let user = {
name: "John",
surname: "Smith"
};
Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
alert(user.fullName); // John Smith
for(let key in user) alert(key);

再次提醒,一个属性要么设置为访问器属性要么设置为数据属性,不能是这两种的结合。

如果我们尝试同时应用getvalue在同一个描述器中,将会报错。

1
2
3
4
5
6
7
8
// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});

Smarter getters/setters

Getters/setters 可以作为真实属性的容器,已获得对其更多的控制。

举个例子,我们想给user禁止设置过短的name属性,我们可以将name属性存储在一个特殊的属性_name。并且在setter中过滤分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("Name is too short, need at least 4 characters");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // Name is too short...

严格来说,外部的代码可以直接通过user._name的形式来访问name。但这里有一个尝试就是:一个属性名由下划线(_)开始,则该属性是内部的,不容许在对象进行操作。

Using for compatibility

在getters和setters之后有一个好主意:它允许控制一个正常的数据属性并且可以随时进行调整。

举个例子:我们给user对象设置两个数据属性分别是nameage

1
2
3
4
5
6
7
8
function User(name, age) {
this.name = name;
this.age = age;
}
let john = new User("John", 25);
alert( john.age ); // 25

不久过后,有了些变化。我们决定将age替换为birthday,因为这样更加便利和准确:

1
2
3
4
5
6
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
}
let john = new User("John", new Date(1992, 6, 1));

现在还要使用age属性的旧代码怎么办?

我们找到所有的地方并修好它们,但如果代码是别人写的那就要费点时间。除此之外,对于user而言存在age是一件好事吗?在一些情境下,这就是我们想要的。

age增加一个getter来减缓这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
// age is calculated from the current date and birthday
Object.defineProperty(this, "age", {
get() {
let todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
}
let john = new User("John", new Date(1992, 6, 1));
alert( john.birthday ); // birthday is available
alert( john.age ); // ...as well as the age

现在旧代码同样可以工作了,我们还额外获得了一个不错的属性。