深入了解对象属性 getters 与 setters
Property getters and setters
有两种对象属性。
第一种是数据属性。我们已经知道如何与他们合作。到目前为止,我们使用的所有属性都是数据属性。
第二种类型的属性是新的东西。访问属性。它们本质上是在获取和设置值时执行的函数,但对于外部代码来说,它们看起来像常规属性。
Getters and setters
访问器属性由“getter”和“setter”方法表示。在对象字面量中,它们由get和set表示:
let obj = {
get propName() {
// getter, the code executed on getting obj.propName
},
set propName(value) {
// setter, the code executed on setting obj.propName = value
}
};
getter
在obj.propName
被读取,setter
-当它被分配。
例如,我们有一个带有name
和姓氏的user
对象:
let user = {
name: "John",
surname: "Smith"
};
现在我们要添加一个fullName
属性,它应该是“John Smith”。当然,我们不想复制粘贴现有的信息,所以我们可以将其作为访问器来实现:
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
}
};
alert(user.fullName); // John Smith
从外部看,访问器属性与普通属性类似。这就是访问器属性的思想。我们不叫用户。作为一个函数,我们通常读取它:getter
在后台运行。
到目前为止,fullName
只有一个getter
。如果我们试图分配user.fullName=
,会有一个错误:
let user = {
get fullName() {
return `...`;
}
};
user.fullName = "Test"; // Error (property has only a getter)
让我们通过为user.fullName
添加setter
来解决这个问题:
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
// set fullName is executed with the given value.
user.fullName = "Alice Cooper";
alert(user.name); // Alice
alert(user.surname); // Cooper
访问器描述符
访问器属性的描述符与数据属性的描述符不同。
对于访问器属性,没有值或可写,而是有get和set函数。
也就是说,访问器描述符可能有:
get——一个没有参数的函数,在读取属性时工作,
set -一个有一个参数的函数,当属性被设置时被调用,
enumerable -与数据属性相同,
configurable -与数据属性相同。
例如,要使用defineProperty
创建访问器的全名,可以使用get
和set
传递描述符:
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); // name, surname
请注意,一个属性可以是访问器(有get/set
方法)或数据属性(有值),而不是两者都有。
如果试图在同一个描述符中同时提供get
和value
,则会出现错误:
// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});
更加智能的 Smarter getters/setters
getter /setter
可以被用作“真实”属性值的包装器,以获得对使用它们的操作的更多控制。
例如,如果我们想禁止对用户使用太短的名称,我们可以有一个setter
名称,并将值保留在单独的属性_name
中:
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...
名字存储在_name
属性中,访问是通过getter
和setter
完成的。
从技术上讲,外部代码可以通过user._name
直接访问该名称。但有一个众所周知的约定,即以下划线“_”开头的属性是内部的,不应该从对象外部接触。
使用的兼容性
访问器的一个重要用途是,它们允许随时控制“常规”数据属性,方法是用getter
和setter
替换它,并调整其行为。
假设我们开始使用数据属性name
和age
来实现用户对象:
function User(name, age) {
this.name = name;
this.age = age;
}
let john = new User("John", 25);
alert( john.age ); // 25
但事情迟早会改变。我们可能会决定存储生日而不是年龄,因为它更精确和方便:
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
}
let john = new User("John", new Date(1992, 6, 1));
那么,如何处理仍然使用年龄属性的旧代码呢?
我们可以尝试找到所有这些地方并修复它们,但这需要时间,而且如果代码被许多人使用的话,就很难做到这一点。此外,年龄对于用户来说是件好事,对吧?
让我们保持它。
为年龄添加getter
可以解决这个问题:
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