JavaScriptのGetter SetterとdefinePropertyを理解する(入門者向け)
本文書ではJavaScriptのGetterとSetterさらにObject.definePropertyについて説明を行っています。ライブラリのコードを読むときや文書を読む時に目にする機会があるので自分で利用する機会がなかったとしても知っていて損はありませんが自分で利用しないとすぐに忘れてしまうので忘れた時はまた読み直してください。
ネット上でGetterとSetterの例に頻繁に利用されているfirstNameとlastNameとfullNameを使って説明を行っています。
目次
フルネームを表示する方法
まずfirstNameとlastNameをもつuser オブジェクトを定義します。
let user = {
firstName: "John",
lastName: "Doe",
};
このユーザのフルネームを表示させたい場合はどうしますか?
変数の結合を利用した方法
1 つ目の方法としてはfirstNameとlastNameを結合することでフルネームを表示することはできます。一番わかりやすく入門者であればこの方法が最初に思い浮かぶと思います。
console.log(user.firstName + " " + user.lastName);
console.log(`${user.firstName} ${user.lastName}`); // ES6から利用できるテンプレートリテラル 今後はこちらを利用
//結果
John Doe
John Doe
フルネームを表示させたい場合にこの長いコードを毎回記述する必要があります。
メソッドを利用した方法
次に考えられるのが、オブジェクトにfullNameメソッドを追加することです。追加したメソッドを実行したい場合はuser.fullNameのカッコをつける必要があります。
let user = {
firstName: "John",
lastName: "Doe",
fullName: function () {
return `${this..firstName} ${this.lastName}`;
},
};
console.log(user.fullName());
//結果
John Doe
Getterを利用した方法
本文書の本題であるGetterを利用すると以下のように記述することができます。こんな方法があるのかと初めて見る人は驚くかもしれません。
let user = {
firstName: "John",
lastName: "Doe",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
};
console.log(user.fullName);
//結果
John Doe
メソッドの場合と異なり実行する際はカッコが必要でなくfirstNameやlastNameのプロパティにアクセスする場合と同じように記述を行うことができます。
このようにGetterを利用することでfirstNameとlastNameを結合し、プロパティのように扱うことができます。
.(ドット)ではなく[]カッコを利用してもアクセスすることが可能です。
console.log(user["fullName"]);
//結果
John Doe
通常のプロパティのようにアクセスできたので値も設定できるのではと考えてしまいます。しかしGetterだけではfullNameに対して値を設定しても値を反映することができません。
user.fullName = "Kit Harington";
値を設定するために利用するのがSetterです。
Setterを利用した値の設定
今回の名前の例では、Setterを利用して値を設定する場合フルーネームを利用します。フルーネームはファーストネームとラストネームの間に空白を挟んでいるので空白を挟んでいるフルネームの値をファーストネームとラストネームの2つに分ける方法を理解しておく必要があります。
この処理は今回の例に限って知っておかなければならないものですが直接Setterに影響を与えるものではありません。
splitメソッドの理解
John Doeというフルネームが与えられたら真ん中に入っている空白によってfirstNameとlastNameをわける必要があります。splitに区切り文字である空白を入れることでfirstNameとlastNameにわけることができます。
fullName = "John Doe";
console.log(fullName.split(" "));
//結果
[ 'John', 'Doe' ]
分割代入を利用すると下記のようにsplitで分けたfirstNameとlastNameを取得することができます。
fullName = "John Doe";
[firstName, lastName] = fullName.split(" ");
console.log(firstName);
console.log(lastName);
Setterを利用した方法
setではfullNameに引数valueを設定し、splitメソッドで文字列を空白で分割し、分割代入でfirstNameとlastNameを設定しています。この時thisをつける必要があります。
let user = {
firstName: "John",
lastName: "Doe",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(" ");
},
};
user.fullName = "Kit Harington";
console.log(user.fullName);
// 結果
Kit Harington
getとsetで同じfullNameを利用していますが、”=”(イコール)で値を設定する場合はsetterが利用され、値を取得する場合はgetterが利用されます。
setterの中で入力した文字列のチェックを行うことも可能です。matchメソッドを利用して入力した文字列に空白がないかチェックをして空白がない場合はメッセージを出力します。
let user = {
firstName: "John",
lastName: "Doe",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
if (!value.match(" ")) {
console.log("入力した文字の名前に空白がありません。");
return;
}
[this.firstName, this.lastName] = value.split(" ");
},
};
user.fullName = "KitHarington";
console.log(user.fullName);
// 結果
入力した文字の名前に空白がありません。
John Doe
Settterで設定するfullNameの文字列に空白を入れた場合はメッセージは出ません。
Object.definePropertyメソッド
Object.definePropertyを利用することでも定義ずみのuserオブジェクトに対してGetterとSetterを追加設定することができます。Object.definePropertyという名前と書式から最初は間違いなく戸惑ってしまうかもしれませんが、これまでのGetterとSetterが理解できていればそれほど難しいものではありません。
下記のコードでは定義済のuserオブジェクトに対して、Object.definePropertyを利用してSetterとGetterを追加設定しています。
let user = {
firstName: "John",
lastName: "Doe",
};
// userは定義済み
Object.defineProperty(user, "fullName", {
get: function () {
return `${user.firstName} ${user.lastName}`;
},
set: function (value) {
[this.firstName, this.lastName] = value.split(" ");
},
});
user.fullName = "Kit Harington";
console.log(user.fullName);
Object.definePropertyの書式は下記の通りです。
Object.defineProperty(オブジェクト, プロパティ名, ディスクリプター)
上記の例ではオブジェクトにはuserオブジェクトが対応し、プロパティ名はアクセスする際に利用するプロパティ名のfullNameが対応し、ディスクリプターには、getとsetを持つオブジェクトが対応しています。
ディスクリプター
Object.definePropertyにはget, set以外にもenumerable, configurable,を設定することができます。enumerable, configurableについてtrue, falseを設定することができます。
true, falseに設定することによりどのような違いがあるか確認します。
enumerableの設定
デフォルトではenumerableはfalseですが、明示的に指定すると以下のように設定することができます。
Object.defineProperty(user, "fullName", {
get: function () {
return `${user.firstName} ${user.lastName}`;
},
set: function (value) {
if (!value.match(" ")) {
console.log("入力した文字の名前に空白がありません。");
return;
}
[this.firstName, this.lastName] = value.split(" ");
},
enumerable: true,
});
forループを利用するとObject.definePropertyで追加したfullNameが表示されます。
for (key in user) {
console.log(key);
}
//結果
{ firstName: 'Kit', lastName: 'Harington', fullName: 'Kit Harington' }
enumerableをfalseに設定するとfullNameは表示されません。
// enumerableがfalseの場合
for (key in user) {
console.log(key);
}
//結果
{ firstName: 'Kit', lastName: 'Harington' }
Object.assignの場合(enumerable)
Object.assignを利用してオブジェクトをマージする際にenumerableがtrueの場合はfullNameはマージされますがfalseの場合はマージに含まれません。
// enumerableがtrueの場合
console.log(Object.assign({}, user));
//結果
{ firstName: 'Kit', lastName: 'Harington', fullName: 'Kit Harington' }
// enumerableがfalseの場合
console.log(Object.assign({}, user));
//結果
{ firstName: 'Kit', lastName: 'Harington' }
configurableの設定
configurableを設定することでdeleteを利用したプロパティの削除を制御することができます。設定する場合はenumerableと同様に以下のように設定を行います。デフォルトではfalseに設定されています。
Object.defineProperty(user, "fullName", {
get: function () {
return `${user.firstName} ${user.lastName}`;
},
set: function (value) {
if (!value.match(" ")) {
console.log("入力した文字の名前に空白がありません。");
return;
}
[this.firstName, this.lastName] = value.split(" ");
},
enumerable: true,
configurable: true,
});
trueの場合は削除することができるのでdeleteした後はundefinedになります。falseの場合は削除できないためfullNameがそのまま表示されます。
//configurable: trueの場合
delete user.fullName;
console.log(user.fullName);
//結果
undefined
//configurable: falseの場合
delete user.fullName;
console.log(user.fullName);
//結果
Kit Harington
まとめ
Getter, Setter, Object.definePropetyの設定方法を簡単な例を利用して説明を行いました。この例を理解していれば自分自身で活用しなかったとしてもコード中にこれらの用語が出てきた場合もコードの理解が進むと思います。実際はコードがもっと複雑なのでGetter, Setter, Object.definePropertyの理解よりもその中で記述されているコードの方が難しいと思いますが。