ECMAScript 2015 で導入された JavaScript クラスは、JavaScript にすでにあるプロトタイプベース継承の糖衣構文です。クラス構文は、新しいオブジェクト指向継承モデルを JavaScript に導入しているわけではありません。
クラスの定義
クラスは実は「特別な関数」であり、関数式と関数宣言の 2 通りで関数が定義できるように、クラス構文にはクラス式とクラス宣言という 2 つの定義方法があります。
クラス宣言
クラスを定義するひとつの方法は、クラス宣言を使うことです。クラスを宣言するには、クラス名 (この例では "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つしか定義できません。2 回以上定義されている場合は、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 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.distance; //未定義
p2.distance; //未定義
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 への追加によって後から作成することができません。
詳しい情報は、 class fields も参照してください。
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.
//NB: For similar methods, the child's method takes precedence over parent's method
クラスは通常の (生成不可能な) オブジェクトを拡張できないことに注意してください。通常のオブジェクトから継承したければ、代わりに 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)) { }
仕様書
ブラウザー実装状況
| デスクトップ | モバイル | サーバー | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
classes | Chrome
完全対応
49
| Edge 完全対応 13 | Firefox 完全対応 45 | IE 未対応 なし | Opera
完全対応
36
| Safari 完全対応 9 | WebView Android
完全対応
49
| Chrome Android
完全対応
49
| Firefox Android 完全対応 45 | Opera Android
完全対応
36
| Safari iOS 完全対応 9 | Samsung Internet Android
完全対応
5.0
| nodejs
完全対応
6.0.0
|
constructor | Chrome
完全対応
49
| Edge 完全対応 13 | Firefox 完全対応 45 | IE 未対応 なし | Opera
完全対応
36
| Safari 完全対応 9 | WebView Android
完全対応
49
| Chrome Android
完全対応
49
| Firefox Android 完全対応 45 | Opera Android
完全対応
36
| Safari iOS 完全対応 9 | Samsung Internet Android 完全対応 5.0 | nodejs
完全対応
6.0.0
|
extends | Chrome
完全対応
49
| Edge 完全対応 13 | Firefox 完全対応 45 | IE 未対応 なし | Opera
完全対応
36
| Safari 完全対応 9 | WebView Android
完全対応
49
| Chrome Android
完全対応
49
| Firefox Android 完全対応 45 | Opera Android
完全対応
36
| Safari iOS 完全対応 9 | Samsung Internet Android 完全対応 5.0 | nodejs
完全対応
6.0.0
|
| Private class fields | Chrome 完全対応 74 | Edge 完全対応 79 | Firefox 未対応 なし | IE 未対応 なし | Opera 完全対応 62 | Safari 未対応 なし | WebView Android 完全対応 74 | Chrome Android 完全対応 74 | Firefox Android 未対応 なし | Opera Android 完全対応 53 | Safari iOS 未対応 なし | Samsung Internet Android 未対応 なし | nodejs 完全対応 12.0.0 |
| Public class fields | Chrome 完全対応 72 | Edge 完全対応 79 | Firefox 完全対応 69 | IE 未対応 なし | Opera 完全対応 60 | Safari 未対応 なし | WebView Android 完全対応 72 | Chrome Android 完全対応 72 | Firefox Android 未対応 なし | Opera Android 完全対応 51 | Safari iOS 未対応 なし | Samsung Internet Android 未対応 なし | nodejs 完全対応 12.0.0 |
static | Chrome
完全対応
49
| Edge 完全対応 13 | Firefox 完全対応 45 | IE 未対応 なし | Opera
完全対応
36
| Safari 完全対応 9 | WebView Android
完全対応
49
| Chrome Android
完全対応
49
| Firefox Android 完全対応 45 | Opera Android
完全対応
36
| Safari iOS 完全対応 9 | Samsung Internet Android 完全対応 5.0 | nodejs
完全対応
6.0.0
|
| Static class fields | Chrome 完全対応 72 | Edge 完全対応 79 | Firefox 完全対応 75 | IE 未対応 なし | Opera 完全対応 60 | Safari 未対応 なし | WebView Android 完全対応 72 | Chrome Android 完全対応 72 | Firefox Android 未対応 なし | Opera Android 完全対応 51 | Safari iOS 未対応 なし | Samsung Internet Android 未対応 なし | nodejs 完全対応 12.0.0 |
凡例
- 完全対応
- 完全対応
- 未対応
- 未対応
- 実装ノートを参照してください。
- 実装ノートを参照してください。
- ユーザーが明示的にこの機能を有効にしなければなりません。
- ユーザーが明示的にこの機能を有効にしなければなりません。
スクラッチパッドでの動作
クラスは再定義できません。スクラッチパッド (Firefox メニュー > Web 開発 > スクラッチパッド) を使っていて、同じ名前のクラスをで 2 回定義しているコードを「実行」すると、SyntaxError: redeclaration of let <class-name>というエラーに戸惑うことでしょう。
定義を再実行するには、スクラッチパッドのメニューから 実行メニュー > 再読込みして実行 を選んでください。
バグ報告 #1428672 への評価をお願いします。

