--- title: クラス slug: Web/JavaScript/Reference/Classes tags: - Classes - Constructors - ECMAScript 2015 - Guide - Inheritance - Intermediate - JavaScript translation_of: Web/JavaScript/Reference/Classes ---
クラスはオブジェクトを作成するためのテンプレートです。それらは、そのデータを処理するためのコードでデータをカプセル化します。JS のクラスはプロトタイプに基づいて構築されていますが、ES5 のクラスライクなセマンティクスとは共有されない構文やセマンティクスも持っています。
クラスは実際には「特別な{{jsxref("Functions", "関数", "", "true")}}」であり、{{jsxref("Operators/function", "関数式", "", "true")}}と{{jsxref("Statements/function", "関数宣言", "", "true")}}を定義することができるように、クラス構文にも{{jsxref("Operators/class", "クラス式", "", "true")}}と{{jsxref("Statements/class", "クラス宣言", "", "true")}}の 2 つの定義方法があります。
クラスを定義するひとつの方法は、クラス宣言を使うことです。クラスを宣言するには、クラス名 (この例では "Rectangle") 付きで class
キーワードを使います。
class Rectangle { constructor(height, width) { this.height = height; this.width = width; } }
関数宣言とクラス宣言の重要な違いは、関数宣言では {{Glossary("Hoisting", "Hoisting")}} されるのに対し、クラス宣言ではされないことです。クラスにアクセスする前に、そのクラスを宣言する必要があります。そうしないと、{{jsxref("ReferenceError")}} が投げられます:
const p = new Rectangle(); // ReferenceError class Rectangle {}
クラスを定義する別の方法はクラス式です。クラス式は、名前付きでも名前なしでもできます。名前付きクラスの名前は、クラス内のローカルとして扱われます。(ただし (インスタンスのではなく) クラスの {{jsxref("Function.name", "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"
注: クラス式にも{{anch("Class declarations", "クラス宣言")}}で言及したのと同じホイスティング問題があります。
中括弧 {}
内にクラス本体を記述します。クラス本体には、メソッドやコンストラクターといったクラスメンバを記述します。
クラス本体は Strict モード で実行されます。つまり、ここで書かれたコードは、パフォーマンスを向上させるために、より厳密な構文に従います。そうでない場合はサイレントエラーが投げられます。なお、特定のキーワードは将来のバージョンの ECMAScript 用に予約されています。
{{jsxref("Classes/constructor", "コンストラクター", "", "true")}}メソッドは、class
で作成したオブジェクトを作成して初期化するための特別なメソッドです。"constructor" という名前の特別なメソッドは、クラスに 1 つしか定義できません。クラスに複数のコンストラクターメソッドが存在する場合、{{jsxref("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
{{jsxref("Classes/static", "static", "", "true")}} キーワードは、クラスの静的メソッドまたはプロパティを定義します。静的メンバー(プロパティとメソッド)は、クラスをインスタンス化せずに呼び出され、クラスインスタンスを介して呼び出すことはできません。静的メソッドは、アプリケーションのユーティリティ関数を作成するためによく使用されますが、静的プロパティは、キャッシュ、固定構成、またはインスタンス間で複製する必要のないその他のデータに役立ちます。
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; } }
フィールドを事前宣言することで、クラス定義はより自己文書化され、フィールドは常に存在するようになります。
上記のように、フィールドはデフォルト値の有無にかかわらず宣言できます。
詳しい情報は、{{jsxref("Classes/Public_class_fields", "パブリッククラスフィールド", "", "true")}}を参照してください。
プライベートフィールドを使うと、宣言は下記のように洗練できます。
class Rectangle { #height = 0; #width; constructor(height, width) { this.#height = height; this.#width = width; } }
プライベートフィールドの参照はクラス本体内でのみ可能となり、クラス外からの参照はエラーとなります。クラス外からは見えないものを定義することで、クラスのユーザーが(変更される可能性のある)内部状態に依存できないようにします。
プライベートフィールドは、事前宣言のみ可能です。
プライベートフィールドは通常のプロパティとは違い、this
への追加によって後から作成することができません。
詳しい情報は、{{jsxref("Classes/Private_class_fields", "プライベートクラスフィールド", "", "true")}}を参照してください。
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. // 同様なメソッドでは、子のメソッドが親のメソッドよりも優先されます。
クラスは通常の (生成不可能な) オブジェクトを拡張できないことに注意してください。通常のオブジェクトから継承したければ、代わりに {{jsxref("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.
Array の派生クラスである MyArray
の中で {{jsxref("Array")}} オブジェクトを返したいときもあるでしょう。species パターンは、デフォルトコンストラクタ-を上書きすることができます。
例えば、デフォルトコンストラクターを返す {{jsxref("Array.map", "map()")}} のようなメソッドを使っているとき、MyArray
ではなく Array
オブジェクトを返したいでしょう。{{jsxref("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)) { }
仕様書 |
---|
{{SpecName('ESDraft', '#sec-class-definitions', 'Class definitions')}} |
{{Compat("javascript.classes")}}
クラスを再定義することはできません。再定義しようとすると SyntaxError
が発生します。
Firefox のウェブコンソール(メニュー > ウェブ開発 > ウェブコンソール)などでコードを試しているときに、同じ名前のクラス定義を 2 回実行すると、SyntaxError: redeclaration of let ClassName
が発生します。(この問題については {{Bug(1428672)}} でさらに詳しく説明しています。)Chrome Developer Tools で同様の操作を行うと、Uncaught SyntaxError: Identifier 'ClassName' has already been declared at <anonymous>:1:1
のようなメッセージが表示されます。