ES2015で追加された構文に、クラスがある。
これを用いることで、JavaScriptでもクラスを使うことが出来る。
ただ、これまでもnew
演算子やprototype
を活用することで、クラスのような機能を実現することが出来ていた。
そしてクラス構文は、こういった機能の糖衣構文であり、内部的な動作は今までと変わらない。
何か新しい機能が追加されたわけではなく、あくまでも、新しい記述方法が追加されたに過ぎない。
概要
クラスの記述方法には、クラス宣言とクラス式の2つがある。
// クラス宣言 class クラス名{ クラスを定義 }; // クラス式 const クラス名 = class{ クラスを定義 };
この2つがあるのは関数と似ているが、関数と違い、クラス宣言によって定義しても巻き上げは行われない。
クラスの定義部分には、メソッドを記述していく。従来とは異なる記法を用いるので、注意が必要。
class Person{ sayHello(){ console.log('Hello!'); }; sayBye(){ console.log('Bye!'); }; };
インスタンスの生成は、new
演算子を使う。これは今までと同様。
class Person{ sayHello(){ console.log('Hello!'); }; sayBye(){ console.log('Bye!'); }; }; let tom = new Person(); tom.sayHello(); // Hello! tom.sayBye(); // Bye!
クラスの定義部分には、メソッドしか記述できない。それ以外のものを記述するとエラーになる。
class Person{ sayHello(){ console.log('Hello!'); }; sayBye(){ console.log('Bye!'); }; this.prop = 1; // エラー console.log('hoge'); // これもエラー };
では、プロパティはどのように定義すればいいのか。
constructor
メソッドを使うことで解決する。
これは特殊なメソッドであり、new
演算子でインスタンスを生成する際に自動的に呼び出される。
各クラスに1つだけ定義することができ、複数定義してあるとエラーになる。
クラス定義部分のthis
は、生成されるインスタンスを指すので、これを用いてプロパティを定義すればいい。
class Person{ constructor(name, age){ this.name = name; this.age = age; console.log('インスタンスを生成しました。'); }; sayHello(){ console.log('Hello!'); }; sayBye(){ console.log('Bye!'); }; sayName(){ console.log(this.name); }; }; const tom = new Person('Tom', 30); // インスタンスを生成しました。 tom.sayName(); // Tom
constructor
メソッドで定義したプロパティは、生成されるインスタンス自身が、それを保持する。
一方、constructor
以外のメソッドは、インスタンス自身は保持せず、プロトタイプに格納される。
各インスタンスは、プロトタイプチェーンによって、メソッドにアクセスする。
class Person{ constructor(name, age){ this.name = name; this.age = age; console.log('インスタンスを生成しました。'); }; sayHello(){ console.log('Hello!'); }; sayBye(){ console.log('Bye!'); }; sayName(){ console.log(this.name); }; }; const tom = new Person('Tom', 30); // インスタンスを生成しました。 tom.sayName(); // Tom // [ 'name', 'age' ] console.log(Object.getOwnPropertyNames(tom)); // [ 'constructor', 'sayHello', 'sayBye', 'sayName' ] console.log(Object.getOwnPropertyNames(Person.prototype));
なお、constructor
のなかでメソッドを定義することも出来る。
プロトタイプに同名のメソッドがあった場合は当然、マスキングが行われる。
class Person{ constructor(name, age){ this.name = name; this.age = age; this.sayHi = () => { console.log('Hi!') }; this.sayBye = () => { console.log('Good Bye!') }; }; sayHello(){ console.log('Hello!'); }; sayBye(){ console.log('Bye!'); }; }; const tom = new Person('Tom', 30); tom.sayHi(); // Hi! tom.sayBye(); // Good Bye! // インスタンス自身がsayByeを持つため、プロトタイプチェーンは発生しない // つまり、'Bye!'と表示するほうのメソッドは呼ばれない
生成されるのはただのオブジェクト
先述のように、クラス構文は糖衣構文であり、何か新しい概念が追加されたわけではない。
そのため、生成されるインスタンスは、従来通りのただのオブジェクトである。
したがって例えば、生成後のプロパティの追加や削除も、問題なく出来る。
class Person{ constructor(name, age){ this.name = name; this.age = age; }; }; const tom = new Person('Tom', 30); console.log(tom.job); // undefined tom.job = 'student'; console.log(tom.job); // student delete tom.job; console.log(tom.job); // undefined
もちろん、seal
やfreeze
を行うことで、それを防止することも出来る。
デフォルト値
ES2015から、引数のデフォルト値を設定できるようになった。
クラス構文のなかでも使うことが出来る。
class Person{ constructor(name='Bob', age='50'){ this.name = name; this.age = age; }; sayMessage(x='Hello!'){ console.log(x); }; }; const tom = new Person('Tom', 30); const bob = new Person(); console.log(tom.name, tom.age); // Tom 30 console.log(bob.name, bob.age); // Bob 50 tom.sayMessage('Hi'); // Hi tom.sayMessage(); // Hello!
ゲッタとセッタ
ゲッタとセッタを設定することも出来る。
ゲッタとセッタそのものについては、以下を参照。
アクセサプロパティ(getterとsetter)
class Person{ constructor(name){ this._name = name; }; get name(){ return this._name; }; set name(x){ this._name = x; }; }; const tom = new Person('Tom'); console.log(tom.name); // Tom tom.name = 'Bob'; console.log(tom.name); // Bob
静的メソッド
クラス構文を使えば、静的メソッドを簡単に定義できる。
静的メソッドとは、インスタンスを作ること無く、クラスから直接呼び出すメソッドのこと。
メソッド名の前にstatic
と記述すると、それが静的メソッドとなる。
let personList = []; class Person{ constructor(name){ this.name = name; personList.push(name); }; static showPersonList(){ console.log(personList); }; }; const tom = new Person('Tom'); const bob = new Person('Bob'); const john = new Person('John'); const taro = new Person('Taro'); // [ 'Tom', 'Bob', 'John', 'Taro' ] Person.showPersonList(); // Person自身が、showPersonListメソッドを持っている // [ 'length', 'name', 'prototype', 'showPersonList' ] console.log(Object.getOwnPropertyNames(Person));
strictモードで動く
クラス構文の中は、特に宣言していなくても、strictモードとなる。
そのため、strictモードを使用していないプログラムにおいては、クラス構文の中だけがstrictモードになるので注意が必要。
strictモードの一例として、プロパティの操作がある。
削除できないプロパティを削除しようとした場合、通常のモードでは、削除は行われないがエラーにもならない。
だがstrictモードで同様のことを行うと、エラーが発生する。
let globalObj = {prop: 1}; Object.freeze(globalObj); delete globalObj.prop; console.log(globalObj.prop); // 1 // 削除されないが、エラーにはならない。 class Test{ deleteProp(){ let obj = {prop: 1}; Object.freeze(obj); delete obj.prop; }; }; let ins = new Test; ins.deleteProp(); // TypeError: Cannot delete property 'prop' of #<Object> // classの中は、strictモード
継承
クラス構文には、継承のための機能も用意されている。
詳しくは以下を参照。
クラス構文(ES2015)による継承とプロトタイプチェーン