オブジェクトプロパティには2種類あります。
1つ目はデータプロパティです。 これらの扱い方はすでに知っています。 これまで使用してきたすべてのプロパティはデータプロパティでした。
2つ目のタイプのプロパティは新しいものです。 それはアクセサプロパティです。 これらは本質的には値の取得と設定時に実行される関数ですが、外部コードからは通常のプロパティのように見えます。
ゲッターとセッター
アクセサプロパティは、「ゲッター」と「セッター」メソッドで表されます。 オブジェクトリテラルでは、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.propName
が読み取られるときに機能し、セッターはそれが代入されるときに機能します。
たとえば、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
を関数として呼び出すのではなく、普通に読み取るだけです。ゲッターは裏で実行されます。
今のところ、fullName
にはゲッターしかありません。 user.fullName=
を代入しようとすると、エラーが発生します。
let user = {
get fullName() {
return `...`;
}
};
user.fullName = "Test"; // Error (property has only a getter)
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
結果として、「仮想」プロパティfullName
ができました。 これは読み書き可能です。
アクセサ記述子
アクセサプロパティの記述子は、データプロパティの記述子とは異なります。
アクセサプロパティの場合、value
またはwritable
はありませんが、代わりにget
およびset
関数があります。
つまり、アクセサ記述子には以下が含まれる場合があります。
get
– 引数のない関数で、プロパティが読み取られるときに機能します。- `set` – 1つの引数を持つ関数で、プロパティが設定されるときに呼び出されます。
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); // name, surname
プロパティは、アクセサ(get/set
メソッドを持つ)またはデータプロパティ(value
を持つ)のいずれかであり、両方を持つことはできないことに注意してください。
同じ記述子にget
とvalue
の両方を指定しようとすると、エラーが発生します。
// Error: Invalid property descriptor.
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});
よりスマートなゲッター/セッター
ゲッター/セッターは、「実際の」プロパティ値のラッパーとして使用して、それらに対する操作をより細かく制御できます。
たとえば、user
の名前が短すぎるのを禁止したい場合は、セッターname
を用意し、値を別のプロパティ_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
プロパティに格納され、アクセスはゲッターとセッターを介して行われます。
技術的には、外部コードはuser._name
を使用して名前に直接アクセスできます。 しかし、アンダースコア"_"
で始まるプロパティは内部的なものであり、オブジェクトの外部から触れてはならないという広く知られた慣習があります。
互換性のための使用
アクセサの優れた用途の1つは、「通常の」データプロパティをいつでもゲッターとセッターに置き換えてその動作を微調整することで、制御できることです。
データプロパティ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
はuser
にあると便利ですよね?
それを維持しましょう。
age
にゲッターを追加すると、問題が解決します。
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
これで古いコードも機能し、便利な追加プロパティができました。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepen…)を使用してください。