クラス
クラスはオブジェクトを作成するためのテンプレートです。それらは、そのデータを処理するためのコードでデータをカプセル化します。JS のクラスはプロトタイプに基づいて構築されていますが、ES5 のクラスライクなセマンティクスとは共有されない構文やセマンティクスも持っています。
クラスの定義
クラス宣言
クラスを定義するひとつの方法は、クラス宣言を使うことです。クラスを宣言するには、クラス名 (この例では "Rectangle") 付きで class
キーワードを使います。
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
ホイスティング(巻き上げ)
関数宣言とクラス宣言の重要な違いは、関数宣言では Hoisting されるのに対し、クラス宣言ではされないことです。クラスにアクセスする前に、そのクラスを宣言する必要があります。そうしないと、ReferenceError
が投げられます:
const p = new Rectangle(); // ReferenceError
class Rectangle {}
クラス式
クラスを定義する別の方法はクラス式です。クラス式は、名前付きでも名前なしでもできます。名前付きクラスの名前は、クラス内のローカルとして扱われます。(ただし (インスタンスのではなく) クラスの name
プロパティによって取得可能)
// 名前なし
let Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// 出力: "Rectangle"
// 名前つき
let Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// 出力: "Rectangle2"
注: クラス式にもクラス宣言で言及したのと同じホイスティング問題があります。
クラス本体とメソッド定義
中括弧 {}
内にクラス本体を記述します。クラス本体には、メソッドやコンストラクターといったクラスメンバを記述します。
Strict モード
クラス本体は Strict モード で実行されます。つまり、ここで書かれたコードは、パフォーマンスを向上させるために、より厳密な構文に従います。そうでない場合はサイレントエラーが投げられます。なお、特定のキーワードは将来のバージョンの ECMAScript 用に予約されています。
コンストラクター
コンストラクターメソッドは、class
で作成したオブジェクトを作成して初期化するための特別なメソッドです。"constructor" という名前の特別なメソッドは、クラスに 1 つしか定義できません。クラスに複数のコンストラクターメソッドが存在する場合、SyntaxError
が投げられます。
スーパークラスのコンストラクターは super
というキーワードで呼び出せます。
プロトタイプメソッド
メソッド定義を参照してください。
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// ゲッター
get area() {
return this.calcArea();
}
// メソッド
calcArea() {
return this.height * this.width;
}
}
const square = new Rectangle(10, 10);
console.log(square.area); // 100
静的メソッドとプロパティ
static キーワードは、クラスの静的メソッドまたはプロパティを定義します。静的メンバー(プロパティとメソッド)は、クラスをインスタンス化せずに呼び出され、クラスインスタンスを介して呼び出すことはできません。静的メソッドは、アプリケーションのユーティリティ関数を作成するためによく使用されますが、静的プロパティは、キャッシュ、固定構成、またはインスタンス間で複製する必要のないその他のデータに役立ちます。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Point";
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined
console.log(Point.displayName); // "Point"
console.log(Point.distance(p1, p2)); // 7.0710678118654755
プロトタイプと静的メソッドによるボクシング
this
に値が付けられずに静的メソッドまたはプロトタイプメソッドが呼ばれると、this
の値はメソッド内で undefined
になります。たとえ "use strict"
ディレクティブがなくても同じふるまいになります。なぜなら、class
本体の中のコードは常に Strict モードで実行されるからです。
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined
Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined
上のコードを従来の関数ベースの構文を使って書くと、非 Strict モードでは、最初の this
の値をもとにして、メソッド呼び出しの中で自動ボクシングが行われます。最初の値が undefined
の場合、this
にはグローバルオブジェクトが入ります。
Strict モードでは自動ボクシングは行われません。this
の値はそのまま渡されます。
function Animal() { }
Animal.prototype.speak = function() {
return this;
}
Animal.eat = function() {
return this;
}
let obj = new Animal();
let speak = obj.speak;
speak(); // グローバルオブジェクト(非厳格モード)
let eat = Animal.eat;
eat(); // グローバルオブジェクト(非厳格モード)
インスタンスプロパティ
インスタンスプロパティはクラスのメソッドの中で定義しなければなりません:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
クラスに付随する静的なプロパティやプロトタイプのプロパティは、クラス本体の宣言の外で定義しなければなりません:
Rectangle.staticWidth = 20;
Rectangle.prototype.prototypeWidth = 25;
フィールド宣言
パブリックフィールドとプライベートフィールドの宣言は JavaScript 標準委員会の TC39 で提案されている実験的機能(ステージ 3)です。ブラウザーでのサポートは限られていますが、この機能は Babel のようなシステムでのビルドステップを通して使用できます。
パブリックフィールド宣言
JavaScript のフィールド宣言構文を使って、上記の例は次のように書くことができます。
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
フィールドを事前宣言することで、クラス定義はより自己文書化され、フィールドは常に存在するようになります。
上記のように、フィールドはデフォルト値の有無にかかわらず宣言できます。
詳しい情報は、パブリッククラスフィールドを参照してください。
プライベートフィールド宣言
プライベートフィールドを使うと、宣言は下記のように洗練できます。
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
プライベートフィールドの参照はクラス本体内でのみ可能となり、クラス外からの参照はエラーとなります。クラス外からは見えないものを定義することで、クラスのユーザーが(変更される可能性のある)内部状態に依存できないようにします。
プライベートフィールドは、事前宣言のみ可能です。
プライベートフィールドは通常のプロパティとは違い、this
への追加によって後から作成することができません。
詳しい情報は、プライベートクラスフィールドを参照してください。
extends
によるサブクラス
extends
キーワードは、クラスを別クラスの子として作成するために、クラス宣言またはクラス式の中で使います。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // スーパークラスのコンストラクターを呼び出し、name パラメータを渡す
}
speak() {
console.log(`${this.name} barks.`);
}
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
サブクラスにコンストラクターが存在する場合は、"this" を使う前に super() を呼ぶ必要があります。
従来の関数ベースの「クラス」も拡張できます:
function Animal (name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(`${this.name} makes a noise.`);
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
// 同様なメソッドでは、子のメソッドが親のメソッドよりも優先されます。
クラスは通常の (生成不可能な) オブジェクトを拡張できないことに注意してください。通常のオブジェクトから継承したければ、代わりに Object.setPrototypeOf()
を使います:
const Animal = {
speak() {
console.log(`${this.name} makes a noise.`);
}
};
class Dog {
constructor(name) {
this.name = name;
}
}
// このコードが無いと、speak() を実行した時に TypeError になります。
Object.setPrototypeOf(Dog.prototype, Animal);
let d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.
Species
Array の派生クラスである MyArray
の中で Array
オブジェクトを返したいときもあるでしょう。species パターンは、デフォルトコンストラクタ-を上書きすることができます。
例えば、デフォルトコンストラクターを返す map()
のようなメソッドを使っているとき、MyArray
ではなく Array
オブジェクトを返したいでしょう。Symbol.species
シンボルを使うと次のように実現できます。
class MyArray extends Array {
// species を親の Array コンストラクターで上書きする
static get [Symbol.species]() { return Array; }
}
let a = new MyArray(1,2,3);
let mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
super
でスーパークラスを呼び出す
super
キーワードを使ってスーパークラスのメソッドを呼び出せます。これはプロトタイプベースの継承よりも優れています。
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(`${this.name} roars.`);
}
}
let l = new Lion('Fuzzy');
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.
ミックスイン
抽象的なサブクラスやミックスインはクラスのためのテンプレートです。ECMAScript のクラスは 1 つだけスーパークラスを持つことができます。そのため、多重継承はできません。機能はスーパークラスから提供されます。
ECMAScript では、スーパークラスをインプットとして、そしてスーパークラスを継承した派生クラスをアウトプットとする関数を mix-in で実装できます:
let calculatorMixin = Base => class extends Base {
calc() { }
};
let randomizerMixin = Base => class extends Base {
randomize() { }
};
ミックスインを使用したクラスを次のように記述することもできます:
class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }
仕様
ブラウザー実装状況
BCD tables only load in the browser
クラス定義の再実行
クラスを再定義することはできません。再定義しようとすると SyntaxError
が発生します。
Firefox のウェブコンソール(メニュー > ウェブ開発 > ウェブコンソール)などでコードを試しているときに、同じ名前のクラス定義を 2 回実行すると、SyntaxError: redeclaration of let ClassName
が発生します。(この問題については バグ 1428672 でさらに詳しく説明しています。)Chrome Developer Tools で同様の操作を行うと、Uncaught SyntaxError: Identifier 'ClassName' has already been declared at <anonymous>:1:1
のようなメッセージが表示されます。