UXPに備えてモダンなJSの書き方に慣れる(オブジェクト class)
ECMA2015以降classベースのオブジェクトの書き方が可能になりました。それまでの関数式ベースの書き方から大きく変わりました(但し中身は同じ)。今回classベースのオブジェクトの書き方を解説します。まずはECMA2015以前の関数式ベースのコンストラクトの作成について。
ご覧の通りECMA5まではコンストラクトの作成は関数をベースにしています。関数の引数にインスタンスのプロパティにしたい値を渡してthisで受け取る、というものでした。thisは所有者のインスタンスによって値が縛られます。このようにコンストラクトをnewした時に初めてthisの参照先が決まようになっています。またオブジェクトはインスタンス自身が該当のメソッド、プロパティを持っていない時にprototypeチェーンを遡って該当のメソッド、プロパティを探してゆきます。例えばArrayコンストラクトから新しい配列のインスタンスを作成した時にjoinやsliceといった所持しているはずのないArrayの標準メソッドが使えたりします。これは子のArrayインスタンスが親のArrayコンストラクトのprototypeメソッドまで遡って見つけるためです。なのでmikeインスタンスもmeowメソッド自体所持していませんがCatコンストラクトのprototypeまで遡ってmeowメソッドを発見して使用します。これがjavascriptのプロトタイプベースのオブジェクトになります。
しかしこのprototypeベースのオブジェクトはjavascript独自の異色(らしく)他言語(他言語よくわからないけど)と似たような感じでclassベースのオブジェクトがjsでもできるようになりました。
constructorでインスタンス作成時にthisのプロパティに渡します。これにより関数式のコンストラクトと同様にインスタンスを作成できます。
Animalのclassから新しいclassを継承して作成する場合もclass構文で簡単にできます。新しいclassをextendsで継承、superで親classを呼び出します。親コンストラクトが引数が必要な場合は必ずsuperを通じて引数を渡します。同じメソッドなんかは子クラスで新しく定義して上書きもできます。
上書きする前の同名のメソッドもオブジェクト内からsuperを通じてアクセスできます。
静的メソッド、プロパティも列挙可能です。静的メソッド、プロパティは主に特定のインスタンスに属さないプロパティ、メソッドになります。静的という名の通り通常のプロパティと違って所有者は常に一定のclassに属します。
staticメソッド内でも所有者のclassを示すためにthisを使用することも可能ですが所有者を明確に示すためにthisではなくclass名を使用するのが好まれます。
親classから継承して作られた子classから親classのstaticにアクセスも可能です。superからアクセスできます。
ここまできてclass構文ができたのだしprivateフィールド、publicフィールドはどうやって利用するのかという疑問が出てくるかもしれませんが実は現状標準のECMAでprivateフィールドは実装されていません。MDNによると実験段階ではあるもののそういう構想はあるみたいです。がどちらにせよ標準では使えません。一応慣習的にはプロパティ、メソッド名の頭に_(アンダースコア)をつけるのが一般的です。
typescriptを使う場合はpublic , private , protectedフィールドが簡単に使えます。class内で各フィールドを宣言するだけです。
また外側から直接触って欲しくないが間接的にプロパティのように外側アクセスできるようにしたい場合getter,setterをメソッドに使えます。メソッドの頭につけるとまるでメソッドがプロパティにアクセスした時のように振る舞います。
ご覧の通りclassは従来のjsとは全く異なるオブジェクトの定義方法ですがprototypeベースのオブジェクトと全く異なる構造なのでしょうか?
Object objectのtoStringメソッドがEmployeeインスタンスのkentaオブジェクトで使えてしまいました。そうです、このclassベースのオブジェクトも結局のところprototypeベースの上に乗っかっただけの構文にすぎないのです。実際MDNの冒頭でもclass構文の説明について「プロトタイプベースの継承を使って、指定された名前の新しいクラスを作成します。」と書かれています。class構文から生成されたオブジェクトもprototypeベースのオブジェクトである以上作成された瞬間にobjectの一番頂点にあたるObject.prototypeからメソッド、プロパティを継承するのです。
javascriptのインスタンスは生成された瞬間に内部__proto__プロパティに親のコンストラクトのprototypeが代入されます。この__proto__プロパティを辿ってゆくことで親の親とコンストラクトを辿ってゆけます。
それではclass構文から作られたオブジェクトはどうでしょうか。
なんとclass構文から生成されたオブジェクトも関数ベースと同じように内部に__proto__プロパティを持っていました。class構文から生成されたオブジェクトも同じprototypeベースである事がわかったと思います。
今後class構文を使う事で関数ベースの構文でオブジェクトを生成することもあまりないかもしれ無いですしprototype自体あまりわかっていなくてもコードが書けるかもしれません。はっきりいって関数ベースで書くよりもclassベースで書く方が初めての方にとってもかなりわかりやすいでしょう。しかしjavascriptがprototypeベースの言語である以上prototypeに関する知識も必要になる、、、とは思います。ちなみにObject objectのようなネイティブオブジェクトをいじったりインスタンスの__proto__プロパティを扱う事自体はあまり推奨されていません。どうしてもいじる場合は細心の注意を払った上で行ってください。
参考
javascriptinfo クラスの継承
MDN 継承とプロトタイプチェーン
gist prototypeと__proto__の違い