プロパティには2種類あります。
最初の種類は データプロパティ です。私たちは既にそれがどうやって動作するのかを知っています。実際、これまで使ってきたすべてのプロパティはデータプロパティでした。
2つ目のプロパティの種類は新しいものです。それは アクセサプロパティ です。それらは基本的には値の取得やセットをする関数ですが、外部コードからは通常のプロパティのように見えます。
Getters と setters
アクセサプロパティは “getter” と “setter” メソッドで表現されます。オブジェクトリテラルでは、それらは get
と set
で表されます:
let obj = {
get propName() {
// getter, obj.propName を取得するときにコードが実行されます
},
set propName(value) {
// setter, obj.propName = value 時にコードが実行されます
}
};
obj.propName
が読まれたときに getter は動作し、setter は割り当てられたときに動作します。
例えば、name
と surname
を持つ user
オブジェクトがあります。:
let user = {
name: "John",
surname: "Smith"
};
今、“John Smith” という値を持つ “fullName” プロパティを追加したいとします。もちろん、既存の情報のコピーペーストはしたくありません。ここで、アクセサを使用してそれを実装することができます。:
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 を追加してそれを修正しましょう。:
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
// set fullName は指定された値で実行されます
user.fullName = "Alice Cooper";
alert(user.name); // Alice
alert(user.surname); // Cooper
今 “仮想” プロパティを持っています。 読み書き可能ですが、実際には存在しません。
プロパティは、 “データプロパティ” か “アクセサプロパティ” のいずれかになりますが、両方にはなりません。
プロパティが get prop()
または set prop()
で定義されると、それはアクセサプロパティです。 なので、getter で読まなければなりません。それに値を割り当てたいならば、setter を使わなければなりません。
setter または getter だけがある場合もあります。この場合は、プロパティの読み込みまたは書き込みはできません。
アクセサディスクリプタ
アクセサプロパティのディスクリプタは、データプロパティと比べて異なります。
アクセサプロパティには、value
も writable
もありませんが、代わりに、get
と set
があります。
したがって、アクセサディスクリプタには次のものがあります:
get
– 引数なしの関数で、プロパティが読まれたときに動作します。set
– 1つの引数をもつ関数で、プロパティがセットされたときに呼ばれます。enumerable
– データプロパティと同じです。configurable
– データプロパティと同じです。
例えば、アクセサ fullName
を 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);
プロパティはアクセサかデータプロパティのいずれかになれますが、両方にはなれないことに再度注意してください。
もしも get
と value
を同じディスクリプタで指定しようとすると、エラーになります。:
// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});
スマートな getters/setters
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
を使うことで、直接 name にアクセスできるかもしれません。しかし、アンダースコア "_"
で始まるプロパティは内部のもので、外部のオブジェクトから触るべきではないということは広く知られています。
互換性のために使用する
getter と setter の裏にある素晴らしいアイデアの1つは、それらは “通常の” データプロパティを制御し、それをいつでも調整することができることです。
例えば、データプロパティ name
と age
を使って user オブジェクトを実装し始めました。:
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
は user
が持っていても良いものですよね?場所によってはそれを必要とするかもしれません。
age
の getter を追加すると、問題が緩和されます:
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
// age は現在の日付と誕生日から計算されます
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 は利用可能です
alert( john.age ); // ...age も同様です
これで古いコードも機能します。
コメント
<code>
タグを使ってください。複数行の場合は<pre>
を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。