ECMAScript 6 で導入された JavaScript クラスは、JavaScript にすでにあるプロトタイプベース継承の糖衣構文です。クラス構文は、新しいオブジェクト指向継承モデルを JavaScript に導入しているわけではありません。JavaScript クラスは、オブジェクトを作成して継承を扱うためのシンプルで明確な構文を用意します。
クラス定義
クラスは実際「特別な関数」であり、関数式と関数宣言で定義するように、クラス構文にはクラス式とクラス宣言という2つの定義方法があります。
クラス宣言
クラスを定義する一つの方法は、クラス宣言を使うことです。クラスを宣言するには、クラス名 (この例では "Polygon") 付きで class キーワードを使います。
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
ホイスティング(巻き上げ)
関数宣言とクラス宣言の重要な違いは、関数宣言では hoisting されるのに対し、クラス宣言ではされないことです。クラスにアクセスする前に、そのクラスを宣言する必要があります。そうしないと、ReferenceError がスローされます:
var p = new Polygon(); // ReferenceError
class Polygon {}
クラス式
クラス式はクラスを定義するもう 1 つの方法です。クラス式は、名前付きでも名前なしでもできます。名前付きクラスの名前は、クラス内部にローカルです。
// 名前なし
var Polygon = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
// 名前つき
var Polygon = class Porygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
注: クラス式にもクラス宣言で言及したのと同じホイスティング問題があります。
クラス本体とメソッド定義
中括弧 {} 内にクラス本体を記述します。クラス本体には、メソッドやコンストラクタといったクラスメンバを記述します。
strict モード
クラス宣言、もしくはクラス式で定義されたクラス本体は、strict モード で実行されます。
コンストラクタ
コンストラクタ は、そのクラスによって定義されるオブジェクトの生成時に、初期化を行う特別なメソッドです。"constructor" という名前のメソッドは、クラスに1つしか定義できません。2 回 以上定義されている場合は、SyntaxError がスローされます。
親クラスのコンストラクタは super というキーワードで呼び出せます。
プロトタイプメソッド
メソッド定義 を参照してください。
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea();
}
calcArea() {
return this.height * this.width;
}
}
const square = new Polygon(10, 10);
console.log(square.area);
静的メソッド
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.sqrt(dx*dx + dy*dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
console.log(Point.distance(p1, p2));
プロトタイプと静的メソッドによるボクシング
"this" にオブジェクトの値が付けられずに静的メソッドまたはプロトタイプメソッドが呼ばれると、"this" の値は関数内で undefined になります。自動ボクシングは行われません。非 strict モードでコードを書く場合でも、同じふるまいになります。
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
let speak = obj.speak;
speak(); // undefined
let eat = Animal.eat;
eat(); // undefined
上のコードを従来の関数ベースのクラスを使って書くと、"this" の値にもとづいて自動ボクシングが行われます。
function Animal() { }
Animal.prototype.speak = function(){
return this;
}
Animal.eat = function() {
return this;
}
let obj = new Animal();
let speak = obj.speak;
speak(); // global object
let eat = Animal.eat;
eat(); // global object
拡張によるサブクラス化
extends キーワードはクラス宣言またはクラス式内で使って、クラスを別クラスの子として作成します。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
speak() {
console.log(this.name + ' 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() {
super.speak();
console.log(this.name + ' barks.');
}
}
var d = new Dog('Mitzie');
d.speak();
クラスはregularな(生成不可能な)オブジェクトを拡張することはできないことに注意してください。regularなオブジェクトから継承したければ、代わりにObject.setPrototypeOf() を使います。
Species
Array の派生型である MyArray の中で Array オブジェクトを返したいときもあるでしょう。species パターンは、デフォルトコンストラクタをオーバライドすることができます。
例えば、map() のようなデフォルトコンストラクタを返すメソッドを使っているとき、MyArray ではなく Array オブジェクトを返したいとします。 Symbol.species シンボルを使うと:
class MyArray extends Array {
// Overwrite species to the parent Array constructor
static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var 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.');
}
}
Mix-in
抽象クラスや mix-in はクラスのためのテンプレートです。ECMAScript のクラスは 1 つだけ親クラスを持つことができます。そのため、多重継承はできません。機能は親クラスから提供されます。
ECMAScript では親クラスをインプットとして、そして親クラスを継承した派生クラスをアウトプットとする関数を mix-in で実装できます:
var CalculatorMixin = Base => class extends Base {
calc() { }
};
var RandomizerMixin = Base => class extends Base {
randomize() { }
};
mix-in を使用したクラスを次のように記述することもできます:
class Foo { }
class Bar extends CalculatorMixin(RandomizerMixin(Foo)) { }
仕様
| 仕様 | 状態 | コメント |
|---|---|---|
| ECMAScript 2015 (6th Edition, ECMA-262) Class definitions の定義 |
標準 | 初期定義。 |
| ECMAScript 2017 Draft (ECMA-262) Class definitions の定義 |
ドラフト |
ブラウザ実装状況
| 機能 | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari | |
|---|---|---|---|---|---|---|
| 基本サポート | 42.0[1] 49.0 |
45 | 13 | 未サポート | 未サポート | 9.0 |
| 機能 | Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile | Chrome for Android |
|---|---|---|---|---|---|---|
| 基本サポート | 未サポート | 45 | ? | ? | 9 | 42.0[1] 49.0 |
[1] strict モードが必要。既定で非 strict モードでのサポートは、"Enable Experimental JavaScript" フラグで無効化されている。