JavaScript のクラス定義
ここでは JavaScript の ES6 で導入された、classキーワードを使ったクラスの定義方法について説明します。
プロトタイプ方式とclassを使ったクラス定義
JavaScript では、従来はプロトタイプを構成することでクラス間の継承関係を定義します。これは「プロトタイプベースのオブジェクト指向プログラミング」などと言われます。
ES6 では、classキーワードを使って、よりわかりやすいと言われる書き方でクラス定義ができるようになりました。
classキーワードによるクラス定義方法が加わりましたが、従来の機能と機能的には同等で書き方が違うだけです。
同じものを、よりわかりやすいとされる構文で書くために使われる構文のことを「シンタックスシュガー」 (糖衣構文) といいます。
プロトタイプベースの書き方と比べて、classを用いる方が Java などのシンタックスに近く、おそらく多くの人にとっては かなり馴染みのあるシンタックスになっています。ES6 が使える環境向けに開発できる場合はclassを利用するのがおすすめです。
クラス定義
ここでは、基本的なクラスの実装方法について、ざっと説明します。細かい部分ではもっといろいろありますので、 各種参考資料等をみてください。
クラス定義とコンストラクタ
クラス名はclassキーワードに続けて書きます。
クラスインスタンスを初期化するメソッドである、コンストラクタの名前はconstructorです。 コンストラクタはクラスに付きひとつだけ定義できます。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
クラス内で自身のインスタンスを表すのはthisです。
上のコードではコンストラクタでthisに対して、nameとageという二つのプロパティを設定しています。 このPersonクラスでは、これらがパブリックフィールドになります。
let p = new Person('Mike', 30);
console.log(p); // Person(name: "Mike", age: 30)
console.log(p.name); // Mike
console.log(p.age); // 30
フィールド
フィールドは事前に定義しておくこともできます。
class Person {
name = '';
age = 0;
constructor(name, age) {
this.name = name;
this.age = age;
}
}
この例でnameとageは共にパブリックフィールドになります。
プライベートフィールドに設定するには、変数名に#を付けます。 パブリックフィールドでは事前定義は任意でしたが、プライベートフィールドは事前定義が必要です。このため後から動的に追加することはできません。
例えば、上の例でageをプライベートフィールドにするには、次のようにします。
class Person {
name = '';
#age = 0;
constructor(name, age) {
this.name = name;
this.#age = age;
}
}
プライベートフィールドが外部からアクセスできないことを確認します。
let p = new Person('Mike', 30);
console.log(p); // Person(name: "Mike", #age: 30)
console.log(p.name); // Mike
console.log(p.age); // undefined
//console.log(p.#age); // エラー
for (let prop in p) {
console.log(`${prop}: ${p[prop]}`);
}
// name: Mike
// (#age は非表示)
スタティックフィールドを定義するには staticをフィールド名の前に付けます。 スタティックフィールドはオブジェクトではなく、クラスに紐付くのでオブジェクトを作らなくてもアクセスできます。
class Person {
static CLASS_NAME = 'PERSON';
}
console.log(Person.CLASS_NAME); // PERSON
メソッド
メソッドはfunctionキーワードなしで名前付き関数を記述するのと、 同様に定義できます。全てパブリックアクセスになり、プライベートメソッドはありません。
class Person {
name = '';
#age = 0;
constructor(name, age) {
this.name = name;
this.#age = age;
}
greet() {
console.log(`Person.greet: ${this.name}, ${this.#age}`);
}
}
このPersonクラスのオブジェクトを作り、greetメソッドを呼ぶには次のようにします。
let p = new Person('Mike', 30);
p.greet(); // Person.greet: Mike, 30
アクセサメソッドを定義しましょう。アクセサメソッドを使うと、プロパティのように値を取得、設定できます。
オブジェクトのプロパティにはデータプロパティとアクセサプロパティの二種類があります。後者の設定をするのがアクセサメソッドです。 プロパティの種類等については「オブジェクトのプロパティ属性」も参考にしてください。
ゲッター (getter) はメソッド名の前に getキーワードを付けて、 引数なしの関数として定義します。
セッター (setter) はメソッド名の前にsetキーワードをつけて、 引数を一つとる関数として定義します。
次の例で get age()とset get(val)は、 プライベートフィールド#ageのアクセサメソッドとして実装されています。アクセサメソッドの名前は任意です。
class Person {
name = '';
#age = 0;
constructor(name, age) {
this.name = name;
this.#age = age;
}
greet() {
console.log(`Person.greet: ${this.name}, ${this.#age}`);
}
get age() {
return this.#age;
}
set age(val) {
if (val >= 0) {
this.#age = val;
}
}
}
アクセサメソッドは次のように、プロパティとして機能します。
let p = new Person('Mike', 30);
p.greet(); // Person.greet: Mike, 30
p.age = 31; // セッターが呼ばれる
p.greet();
p.age = -1; // ゲッターが呼ばれる
p.greet();
スタティックメソッド はクラスに静的に紐付いているメソッドです。
オブジェクトがなくても呼び出せますが、インスタンスがないのでthisにアクセスできません。
継承
クラスの継承はextendsキーワードを使って、基底クラスを指定することによって定義します。 派生クラスの中で基底クラスのインスタンスとして、superキーワードでアクセスできます。
コンストラクタ内で基底クラスのコンストラクタはsuper()としてアクセスできます。通常、コンストラクタ内では最初にsuperを呼び、基底クラスの初期化を行います。
次の例では、上のPersonクラスを派生して、Employeeクラスを定義します。
class Employee extends Person {
constructor(name, age, title) {
super(name, age);
this.title = title;
}
}
このEmployeeオブジェクトを作成して、greetメソッドを呼んでみましょう。
let e = new Employee('Kevin', 45, 'Manager');
e.greet(); // Person.greet: Kevin, 45
Employeeで新しく追加したtitleフィールドの値も出力するようにgreetメソッドをオーバーライドしましょう。
派生クラスから基底クラス内のプライベートフィールドにはアクセスできません。このためageアクセサメソッドを使ってageの値を取っています。
class Employee extends Person {
constructor(name, age, title) {
super(name, age);
this.title = title;
}
greet() {
console.log(`Employee.greet: ${this.name}, ${this.age}, ${this.title}`);
}
}
動作確認をすると次のようになります。
let e = new Employee('Kevin', 45, 'Manager');
e.greet(); // Employee.greet: Kevin, 45, Manager
以上、ES6 で新しく導入されたclassキーワードによる、クラス定義の書き方を駆け足でみてみました。