Javascript对象有两种属性,一种是数据属性,我们经常使用比较熟悉;第二种是访问器属性,本质就是获取和设置值的函数,但从代码上好像是正常属性。
访问器属性通过”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
}
};
当obj.proName
调用是,getter方法读取属性,赋值时setter方法调用。示例,我们有user
对象,有name
、surname
两个属性。
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
从外面看,访问器属性好像正常属性,这是访问器属性的思想。我们没有作为一个函数调用user.fullName
,只是正常读取:getter在后台运行。
现在,fullName
仅有一个getter,如果我们打算赋值user.fullName=
,则会报错。让我们来增加setter给user.fullName
修改错误。
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 prop()
或在set prop()
,则为访问器属性。所以必须通过getter读取、setter赋值。
有时仅有setter或getter,这时正常的,只是这时不能读取或赋值。
访问器描述符是不同的。对访问器属性,没有value
和writable
,代替他们的是get
和set
函数。
访问器描述可能有:
get
– 无参函数,读取属性时调用,set
– 无参函数, 给属性赋值时调用,enumerable
– 与数据属性一致,configurable
– 与数据属性一致.示例,使用defineProperty
创建fullName
访问器时,可以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);
再次提醒,属性只能是访问器或数据属性,不能都是。如果我们视图给描述符同时提供get
和value
,会报错:
// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});
getter/setter能用作“真实”属性的包装器,实现更多的控制。例如,如果我们想避免user
的名称太短,可以存储name
在一个特殊属性_name
中,然后使用setter过滤赋值。
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
,但有个普遍的共识,使用“_”开头的属性,仅在内部使用,外部对象不要直接访问。
兼容性用途
getter和setter的一个好处是,他们可以控制正常数据属性,并在任何时候调整。举例,我们开始实现user
对象使用数据属性name
和age
:
function User(name, age) {
this.name = name;
this.age = age;
}
let john = new User("John", 25);
alert( john.age ); // 25
但是很快或后来,需求变了,代替age
,我们可能决定去存储birthday
,因为更严格、方便:
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
}
let john = new User("John", new Date(1992, 6, 1));
现在,如何出来原来的代码,他们仍然在使用age
属性?
我们可以尝试找到所有age的代码并修改他们,但这太费时,且如果代码是别人写的,很难去做。另外name
属性对user
对象来说并不多余,在有些地方正是我们需要的,给age
增加一个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
现在我们多了一个属性,而且原来的代码也正常工作。