クラスとは、オブジェクト指向プログラミング(OOP)の中心的な概念で、属性(プロパティ)と動作(メソッド)を1つにまとめた設計図のようなものです。クラスを使用することで、コードの再利用性が高まり、管理や保守が容易に。クラスは具体的なオブジェクト(インスタンス)を生成するための「型」として機能します。
以下のJavaScriptコードは、Car
というクラスを定義し、それを使用して具体的な車オブジェクトを作成する例です。
class Car { constructor(brand, model) { this.brand = brand; this.model = model; } displayInfo() { console.log(`This car is a ${this.brand} model ${this.model}.`); } } // Carクラスのインスタンスを生成 const myCar = new Car('Toyota', 'Corolla'); myCar.displayInfo(); // 出力: This car is a Toyota model Corolla.
このページでは↑のサンプルコードの意味・内容を初心者でも理解できるように、Javascriptにおけるクラスの基本を1からわかりやすくご説明します。Webエンジニア/Webデザイナーを目指す方であれば知らないと恥ずかしい超・基本知識の1つです。是非最後までご覧ください。
参考 【JavaScript入門】基本文法/基礎文法を5分で
Javascript:クラスとは?
クラスとはオブジェクトを作成するための「設計書」のこと。
簡単な日常生活の例を使ってご説明します。
まず、クラスとは何かを理解するために、車を作る工程を思い浮かべてみてください。あなたは自動車メーカーに勤めていて、新型の車を作りたいと思ったとしましょう。その際、最初に行う工程が「設計図を作成すること」になるはずです。いきなり車の部品を作り始めるのではなく、車の「形」や「サイズ」「色」や「部品の構成」に加えて、アクセルやブレーキなど、どのような機能を持つべきかを定義する必要があるでしょう。
自動車メーカーは車を1台だけ作ればよいわけではなく、100台~10000台と製造する必要があるので、設計図を作成することは重要な作業になります。
結論を言うと「クラスとは、この設計図のようなもの」です。
どのようなデータ(属性)を持ち、どのような動作(メソッド)をするべきかを定義するのが、クラスを定義するということになります。
車の設計図の例で言えば、データ(属性)とは「車の色」「車種」「製造年」などがそれに該当します。動作(メソッド)とは「アクセルを踏む」、「ブレーキを踏む」、「ハンドルを切る」などを指します。
車の設計図の例で言えば、データ(属性)とは「車の色」「車種」「製造年」などがそれに該当します。動作(メソッド)とは「アクセルを踏む」、「ブレーキを踏む」、「ハンドルを切る」などを指します。
ひとまず、ここでは何となく「クラスってそういうものなんだな」とイメージできればOKです。で、早速Javascriptでクラスを定義する方法を見ていきましょう。
Javascript:class
Javascriptでクラスを定義するにはclass
キーワードを使用します。ちなみに、クラス名の最初の文字は大文字で始めるのが一般的です。
class FirstClass { constructor() { // コンストラクタメソッド: 新しいインスタンスを作成する際に実行されます。 // インスタンス固有の初期化処理をここで行います。 } method1() { // メソッド1の定義: クラスのインスタンスが利用できる機能を提供します。 } method2() { // メソッド2の定義: 別の機能を提供します。 } }
この例では、FirstClass
という名前のクラスを定義しています。このクラスには以下のような基本的な構成要素を含みます。
- constructor()メソッド: クラスから新しいオブジェクトが作成されるたびに自動的に実行される特別なメソッド。このメソッド内で、そのインスタンスの初期状態を設定するためのコード(例えば、プロパティの初期値を設定するなど)を書きます。
- メソッドの定義:
method1()
やmethod2()
のように、クラス内にメソッドを定義することができます。これらのメソッドは、クラスのインスタンスが利用できる機能や振る舞いを実装します。
サンプルコード1 Dog
クラスの定義
class Dog { constructor(name, breed) { this.name = name; // 犬の名前 this.breed = breed; // 犬の種類 } bark() { console.log(`${this.name} says woof!`); } }
コンストラクタ(constructor)
コンストラクタとは、オブジェクト指向プログラミングに共通して存在する重要な概念の1つで、クラスからオブジェクト(インスタンス)を生成する際に自動的に呼び出される特別なメソッドです。コンストラクタの主な役割は、新しく作成されたオブジェクトの初期化を行うこと。これには、オブジェクトのプロパティに初期値を設定する作業が含まれます。
ポイント コンストラクタの特徴
- 自動的に呼び出される: インスタンスが生成される時に自動的に呼び出される。この性質により、オブジェクトが適切に初期化されていることを保証することが可能になる。
- 初期化の役割: オブジェクトのプロパティや状態に初期値を設定。→オブジェクトが正しく機能するための準備が整う。
- 直接呼び出しは不可: コンストラクタは、
new
キーワードを使用してクラスからオブジェクトを生成する際にのみ内部的に呼び出されます。クラスの外部から直接呼び出すことはできません。
以下は、JavaScriptにおける簡単なPerson
クラスの定義と、そのコンストラクタの使用例です。
class Person { constructor(name, age) { this.name = name; // プロパティに初期値を設定 this.age = age; // 同上 } greet() { console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`); } } // Personクラスからインスタンスを生成し、コンストラクタが自動的に呼び出される const person1 = new Person('John', 30); // greetメソッドを呼び出して、コンストラクタによって設定された値を使用 person1.greet(); // 出力: Hello, my name is John and I am 30 years old.
参考 ${テンプレートリテラル} / メソッドの呼び出し
この例では、Person
クラスのコンストラクタが、新しいPerson
オブジェクトが作成される際にname
とage
のプロパティに初期値を設定しています。その後、greet
メソッドを通じて、これらのプロパティがどのようにオブジェクト内で使用されるかを示しています。
コンストラクタを使用することで、オブジェクトが期待する状態で生成されることを保証し、プロパティの値が適切に設定されていることを確認できます。これにより、オブジェクトの安定した動作と、コードの保守性の向上が期待できます。
インスタンスの作成
定義したクラスはそのままでは利用できません。インスタンスを作成したとき始めて実体化します。ここでは、インスタンス化についてご説明します。
「インスタンス化」とは、クラス(オブジェクトの設計図)をもとにして、具体的なオブジェクト(インスタンス)を生成するプロセスのことを指します。オブジェクト指向プログラミングにおいて、クラスは属性(プロパティ)と動作(メソッド)を定義するテンプレートのようなものですが、クラス自体は具体的なデータや状態を持ちません。インスタンス化を通じて、この抽象的な定義から実際に動作するオブジェクトを作り出します。
ポイント インスタンス化のプロセス
- クラスの定義: まず、オブジェクトの構造や振る舞いを定義するクラスを作成。
- インスタンスの生成: クラスから新しいインスタンスを生成するには、
new
キーワードを使ってクラスをインスタンス化する。このステップでコンストラクタが呼び出され、オブジェクトの初期化が行われる。 - インスタンスの使用: インスタンス化されたオブジェクトは、定義されたプロパティやメソッドを持ち、プログラム内で自由に使うことができる。
class FirstClass { ~~~ } // FirstClassのインスタンスを作成 const firstInstance = new FisrtClass(); // インスタンスのメソッドを呼び出す firstInstance.method1(); firstInstance.method2();
new
キーワードを使ってFirstClass
のインスタンスを作成し、その後でインスタンスに対して定義されたメソッドを呼び出しています。これにより、クラスを利用した具体的なオブジェクトが生成され、そのオブジェクト固有の機能を使用することができます。
サンプルコード2 Dog
クラスのインスタンス化と呼び出し
class Dog { constructor(name, breed) { this.name = name; // 犬の名前 this.breed = breed; // 犬の種類 } bark() { console.log(`${this.name} says woof!`); } } // `Dog`クラスからインスタンスを作成 const myDog = new Dog("Buddy", "Golden Retriever"); // インスタンスのメソッドを呼び出す myDog.bark(); // 出力: Buddy says woof!
インスタンスの作成にはnew
キーワードを使用し、Dog
クラスに定義されたbark
メソッドを呼び出しています。この方法により、オブジェクト指向プログラミングの基本的な概念である「カプセル化」「継承」「ポリモーフィズム」を活用し、効率的かつ整理されたコードを実現できます。
ここまでの説明でクラスの基本的な概念と使い方についてはアルテ色理解が深まったと思います。クラスの定義、コンストラクタの役割、インスタンス化のプロセス、そしてプロパティとメソッドの定義と使用方法。これらはオブジェクト指向プログラミングにおいて非常に重要な概念であり、JavaScriptをはじめとする多くのプログラミング言語で広く使われています。
ただし、クラスを完璧に理解するためには、以下のようなさらに高度なトピックも存在します。ここからは、↓についていくつかサンプルコードをもとに深堀して解説していきます。
概念 | 説明 |
---|---|
継承 | クラス間でコードを再利用するためのメカニズム。基底クラスのプロパティやメソッドを派生クラスが継承する。 |
カプセル化 | クラス内のデータ(プロパティ)と振る舞い(メソッド)を一つにまとめ、外部からの直接的なアクセスを制限する方法。 |
ポリモーフィズム | 異なるクラスのオブジェクトが同じインターフェースを通じて異なる振る舞いを実現する概念。 |
静的メソッドとプロパティ | インスタンスを生成せずにクラス自体に紐づけられるメソッドやプロパティ。 |
継承(extends)
継承とは、あるクラス(基底クラスまたは親クラスと呼ばれる)の特性(プロパティとメソッド)を別のクラス(派生クラスまたは子クラスと呼ばれる)に引き継ぐメカニズムです。継承を利用することで、既存のコードの再利用、プログラムの構造の整理、および複雑性の管理が容易になります。
JavaScriptでの継承の簡単な例を以下に示します。クラスの継承を行う際にはextends
キーワードを使用します。
// 基底クラス(親クラス) class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a sound.`); } } // 派生クラス(子クラス) - Animalクラスを継承 class Dog extends Animal { constructor(name) { super(name); // 親クラスのコンストラクタを呼び出し } speak() { console.log(`${this.name} barks.`); } } const dog = new Dog('Rex'); dog.speak(); // 出力: Rex barks.
↑の例では、Animal
クラスが基底クラスとして定義されており、Dog
クラスがAnimal
クラスを継承しています。Dog
クラスはAnimal
クラスのconstructor
とspeak
メソッドを継承していますが、speak
メソッドをオーバーライド(上書き)して独自の振る舞いを定義しています。
super
クラスを継承して利用する際の「super」キーワードについてご説明します。
super
は、派生クラス(子クラス)から基底クラス(親クラス)のコンストラクタやメソッドにアクセスするために使用されるキーワードです。super
の使用目的は主に以下の二つに分けられます。
パターン1:コンストラクタ内でのsuper
派生クラスのコンストラクタ内でsuper
を使用すると、基底クラスのコンストラクタを呼び出すことができます。これは、派生クラスで基底クラスのコンストラクタを実行して初期化処理を行う必要がある場合に使用します。派生クラスのコンストラクタ内でsuper
を呼び出さないと、基底クラスのプロパティが適切に初期化されず、エラーが発生する可能性があります。
class Animal { constructor(name) { this.name = name; } } class Dog extends Animal { constructor(name, breed) { super(name); // 基底クラスのコンストラクタを呼び出し、nameプロパティを初期化 this.breed = breed; // 追加のプロパティを初期化 } }
パターン2:メソッド内でのsuper
派生クラスのメソッド内でsuper
を使用すると、基底クラスに定義されたメソッドにアクセスできます。これは、派生クラスで基底クラスのメソッドの振る舞いを再利用したい、または拡張したい場合に便利です。super
を用いることで、基底クラスのメソッドをオーバーライド(上書き)しつつ、その元の実装を呼び出すことが可能になります。
class Animal { speak() { console.log("Animal makes a sound."); } } class Dog extends Animal { speak() { super.speak(); // 基底クラスのspeakメソッドを呼び出し console.log("Dog barks."); } } const dog = new Dog(); dog.speak(); // 出力: // Animal makes a sound. // Dog barks.
super
は派生クラスから基底クラスのコンストラクタやメソッドにアクセスし、それらを利用するための強力な機能を提供する、と理解できればOKです。
カプセル化
JavaScriptにおける「カプセル化」は、オブジェクトの詳細を外部から隠し、外部から直接アクセスできる部分を制限するオブジェクト指向プログラミングの原則の1つです。カプセル化によってオブジェクトの内部状態を保護し、外部からの不正なアクセスや予期しない変更を防ぐことができます。これにより、オブジェクトの利用者は、そのオブジェクトが提供する公開インターフェース(メソッドやプロパティ)を通じてのみオブジェクトとやり取りすることになります。
カプセル化することで、そのクラスが正常に動くために「変えてはいけない部分を変えられなくする」ことができるということです。
JavaScriptにおけるカプセル化の実現方法
JavaScriptでカプセル化を実現する方法はいくつかありますが、一般的な方法は以下の通りです。
パターン1 プライベートフィールドとメソッド(ES2020以降)
最新のJavaScriptでは、クラス内でプライベートフィールドやメソッドを定義するために#
プレフィックスを使用します。これにより、クラスの外部からはアクセスできなくなります。
class Person { #name; // プライベートフィールド constructor(name) { this.#name = name; } getName() { return this.#name; // プライベートフィールドへのアクセス } } const person = new Person('John'); console.log(person.getName()); // John console.log(person.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class
プロパティ「#name」はカプセル化されたので、外部から直接参照することはできません。そのため、最後の行で「#name」を参照している個所はエラーとなっていることが分かります。
パターン2 クロージャを使用する
クロージャを利用してプライベート変数や関数を作成することもできます。この方法は、ES2020以前から利用可能で、関数スコープを活用します。
このページで解説している「クラス」とは直接的に関係ありませんか「カプセル化」の観点で合わせて覚えておきたいテクニックです。
function Person(name) { let privateName = name; // プライベート変数 this.getName = function() { return privateName; }; } const person = new Person('John'); console.log(person.getName()); // John console.log(person.privateName); // undefined
privateName は関数内でのみ有効なので、これは外部から参照することはできないということです。
カプセル化の利点は「安全性: オブジェクトの内部状態が外部から直接変更されることを防ぐ」「メンテナンス性: 内部実装の変更が外部のコードに影響を与えにくくなる」「使用の単純化: 利用者はオブジェクトの公開インターフェースだけを理解すればOK(内部の複雑さを知る必要がない)」という点にあります。
カプセル化は、プログラムの堅牢性を高め、複雑性を管理するのに役立つ重要な概念です。JavaScriptではプライベートフィールドやクロージャを使った方法でカプセル化を実現し、より安全でメンテナンスしやすいコードを書くことができます。
ポリモーフィズム
ポリモーフィズム(多態性)は、異なるクラスのオブジェクトが同一のインターフェースやメソッドを通じて異なる動作をする能力を指します。ポリモーフィズムを利用することで、コードの再利用性を高め、柔軟性と拡張性を向上させることができます。
サンプル ポリモーフィズムの例
class Animal { speak() { console.log("Animal speaks"); } } class Dog extends Animal { speak() { console.log("Dog barks"); } } class Cat extends Animal { speak() { console.log("Cat meows"); } } function makeAnimalSpeak(animal) { animal.speak(); // 実行時にオブジェクトの実際のクラスに基づいて適切なメソッドが呼び出される } const dog = new Dog(); const cat = new Cat(); makeAnimalSpeak(dog); // 出力: Dog barks makeAnimalSpeak(cat); // 出力: Cat meows
↑の例では、Animal
クラスを継承したDog
クラスとCat
クラスが異なるspeak
メソッドの実装を持っています。makeAnimalSpeak
関数はAnimal
型のパラメータを取りますが、実際にはそのサブクラスのインスタンスを受け取り、オーバーライドされたメソッドを実行します。これにより、異なるクラスのオブジェクトが同一のインターフェース(この場合はspeak
メソッド)を通じて異なる動作を示すポリモーフィズムが実現されています。
ポリモーフィズムの主な形態
- サブタイプポリモーフィズム(継承ポリモーフィズム)
- サブタイプポリモーフィズムは、基底クラス(親クラス)の参照を通じて派生クラス(子クラス)のインスタンスを扱うことができる性質です。派生クラスは基底クラスのメソッドをオーバーライド(上書き)することで、異なる振る舞いを実現します。
- アドホックポリモーフィズム(オーバーロードポリモーフィズム)
- 同じ関数名でも、引数の型や数に応じて異なる処理を行うことを指します。JavaScriptでは関数のオーバーロードは直接的にはサポートされていませんが、引数の数や型をチェックすることで同様の動作を実現できます。
ポリモーフィズムを利用することで、プログラムの柔軟性を高め、異なるクラスのオブジェクトを一つのインターフェースを通じて扱うことが可能になります。これは、特に大規模なアプリケーションやライブラリの設計において、コードの簡潔さと再利用性の向上に寄与します。
静的メソッドとプロパティ
静的メソッドとプロパティは、クラスのインスタンスではなくクラス自体に属するメソッドとプロパティです。これらはクラスのインスタンスを生成することなく直接クラスからアクセスすることができます。静的メソッドとプロパティは、インスタンスに依存しない操作を行いたい場合や、クラスレベルで共有するデータを持つ場合に便利です。
静的メソッド
- 静的メソッドは、クラスのインスタンス化なしに呼び出すことができるメソッド。
static
キーワードを使ってクラス内で定義されます。- インスタンス固有のデータ(プロパティ)にはアクセスできませんが、静的プロパティにはアクセス可能です。
- ユーティリティ関数やファクトリーメソッドなど、インスタンスデータに依存しない処理を実装するのに適しています。
静的プロパティ
- 静的プロパティは、クラスのインスタンス間で共有されるクラスレベルのデータです。
static
キーワードを用いて定義され、クラス名を通じてアクセスします。- すべてのインスタンスに共通の値を保持したい場合や、設定値などをクラスに紐づけて管理したい場合に使用されます。
class MyClass { static staticProperty = 'クラスレベルのプロパティ'; static staticMethod() { return '静的メソッドが呼び出されました'; } } console.log(MyClass.staticProperty); // クラスレベルのプロパティ console.log(MyClass.staticMethod()); // 静的メソッドが呼び出されました
この例では、MyClass
に静的プロパティと静的メソッドを定義しています。これらはMyClass
のインスタンスを生成せずに直接アクセスすることができます。
静的メソッドとプロパティは、インスタンスに依存しない操作を行いたい場合や、クラス全体で共有する情報を管理する場合に特に有用です。しかし、インスタンス固有のデータにアクセスする必要がある場合は、非静的メソッドやプロパティを使用する必要があります。