JavaScriptは多くの開発者に愛用されているプログラミング言語ですが、オブジェクト指向プログラミング(OOP)の概念を取り入れたクラス(class)の利用法は、十分に知られているとは言い難いように思われます。
本記事では、オブジェクト指向プログラミングの第一歩を踏み出すための手助けとなるべく、JavaScriptにおけるclassの基本から応用までをわかりやすく解説します。
目次
EcmaScript 2015(ES6)で導入されたclass
JavaScriptのclassは、EcmaScript 2015(ES6)で導入されました。
classはオブジェクト指向プログラミングを容易にするための糖衣構文(シュガーシンタックス)であり、実際には関数の上に構築されています。
つまり、クラスの背後では関数が動作していますが、より直感的で使いやすい構文が提供されています。
本記事では、JavaScriptに class が導入された EcmaScript2015からEcmaScript2021までの class について解説しています。
なお、EcmaScript2022以降の class については、別の記事で解説しています。
classを知るメリット
JavaScriptでclassについて学んでおくことには多くのメリットがあります。
- 他言語(JavaやPython)を理解しやすくなる: classの概念は多くのオブジェクト指向プログラミング言語で共通しています。JavaScriptでのclassを理解しておくことで、JavaやPythonなどの他言語のclassの使い方も理解しやすくなります。
- TypeScriptを学習する上でも必要となる: TypeScriptはJavaScriptのスーパーセットであり、classの概念も含まれています。TypeScriptは近年の規模の大きいプロジェクトの多くで採用されている他、ReactやNext.jsを使用する際のデファクトスタンダードになっていると言っても過言ではありません。また、型安全なコードを書きたい場合においても、classの理解は不可欠です。
- フレームワークのReactなどでも使用される: Reactはclassコンポーネントと呼ばれるコンポーネントの定義方法を提供しています。最新のReactでは関数コンポーネントが主流ですが、既存のコードベースや特定のシナリオではclassコンポーネントが使われることもあります。
classの基本
オブジェクト指向プログラミングにおける class とは、オブジェクト(モノ)の設計図にあたります。
家を例に考えるとわかりやすいですが、1つの設計図から、多くの家が建てられますよね。
実際に建てられた家の一軒一軒が、実体(インスタンス)です。
class は設計図であり、実際に動作するのは実体(インスタンス)であるということの理解が重要です。
定義方法 (ES 2021まで)
classに定義できるのは、属性(プロパティ)と動作(メソッド)です。
以下は基本的なclass定義の例です。
名前(name)と年齢(age)をプロパティとして持ち、「挨拶する」というメソッド greet を持っています。
// 👇class定義:ここから
class Person {
constructor(name, age) {
this.name = name; // this.name がプロパティ
this.age = age; // this.age がプロパティ
}
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
}
// 👆class定義:ここまで
// 👇classから実体(インスタンス)を生成している
const person = new Person('Luke', 18);
// 👇生成されたインスタンスからメソッドを呼び出している
person.greet(); // 実行結果: Hello, my name is Luke.
コードの解説
他のオブジェクト指向プログラミング(Java言語など)の経験者であれば、上記のサンプルコードだけである程度の理解は見込めるかと思いますが、「初めて学習する言語がJavaScript」という方のためにコードの内容を解説します。
1. classの定義
// クラスの定義には class キーワードを使用します。
// 開始の波カッコ({)から終了の波カッコ(})の内側にプロパティやメソッドを記述します。
// この例では Person(人間)クラスを定義しています。
class Person {
...
}
2. constructor(コンストラクタ)
constructor(コンストラクタ)とはclassのインスタンス(実体)が生成されるタイミングで呼び出される特別なメソッドです。
constructor内で、classの各プロパティ(今回の場合は this.name と this.age)を初期化します。
constructor(name, age) {
this.name = name; // this.name がプロパティ
this.age = age; // this.age がプロパティ
}
3. メソッド
メソッドと関数はほぼ同義です。
あるオブジェクトの操作として定義された関数をメソッドと呼び分けています。
今回の場合であれば、greetは、Personの操作として定義されています。
greet() {
// インスタンスの name プロパティにアクセスします。
console.log(`Hello, my name is ${this.name}.`);
}
ここまでが、class を定義しているコードの説明です。
4. インスタンスの生成とメソッドの呼び出し
ここからは、class を使用しているコードの説明です。
classは設計図なので、実体(インスタンス)ではありません。
建築された家(=インスタンス)には住めますが、家の設計図(=class)に住むことはできないのと同じです。
インスタンスを生成するには、new 演算子を使用します。
そして、new 演算子によりインスタンス生成するタイミングで、対象classのコンストラクタが呼び出されます。
// 👇classから実体(インスタンス)を生成している
// このタイミングでconstructorが呼び出される
const person = new Person('Luke', 18);
// 👇生成されたインスタンスからメソッドを呼び出している
person.greet(); // 実行結果: Hello, my name is Luke.
classの継承
継承を使用して他のclassの機能を拡張できます。継承はextendsキーワードを使用して実現します。
親から子へ受け継がれる関係と似ていることから、継承されるclassを親クラス(スーパークラス)、継承するclassを子クラス(サブクラス)と呼びます。
「classの基本」で作成したclassを継承するサンプルプログラムを提示します。
class Employee extends Person {
constructor(name, age, job) {
super(name, age);
this.job = job;
}
work() {
console.log(`${this.name}の仕事は${this.job}です。`);
}
}
const employee = new Employee('Jaye', 28, 'システム開発');
employee.greet(); // 実行結果: Hello, my name is Jaye.
employee.work(); // 実行結果: Jayeの仕事はシステム開発です。
コードの解説
1. 継承の定義
extendsキーワードを使用して、Employee クラスが Person クラスを継承していることを示します。
これにより、Personが親クラス(スーパークラス)、Employeeが子クラス(サブクラス)という関係になり、親クラスのPersonに定義されているプロパティやメソッドを、子クラスのEmployeeは改めて定義することなく使用することができるようになります。
class Employee extends Person {
2. constructor(コンストラクタ)
Employeeクラスのコンストラクタでは、superキーワードを使用して親クラス(Person)のコンストラクタを呼び出しています。
Personにはないプロパティ(job)のみを自身のプロパティ(this.job)として定義しています。
constructor(name, age, job) {
super(name, age);
this.job = job;
}
3. Employee固有のメソッドを定義
親クラスのPersonにはないEmployee固有の新しいメソッド(work)を定義しています。
親クラスのPersonでnameプロパティが定義されているため、Employeeでは改めて定義しなおすことなく this.name を使用できていることがわかります。
work() {
console.log(`${this.name}の仕事は${this.job}です。`);
}
アクセサメソッド(getterとsetter)
アクセサを使用すると、プロパティの値を取得(getter)、設定(setter)するための追加ロジックを定義できます。
class Product {
constructor(name, price) {
this._name = name;
this._price = price;
}
// 👇priceという名前のgetterメソッドを定義しています。
get price() {
return this._price;
}
// 👇priceという名前のsetterメソッドを定義しています。
set price(newPrice) {
if (newPrice > 0) {
this._price = newPrice;
} else {
console.log('価格には1以上の整数を設定してください。');
}
}
}
const product = new Product('Laptop', 1000);
console.log(product.price); // 実行結果: 1000
// 👆プロパティを参照しているかに見えますが、getterメソッドを呼び出しています。
product.price = 1200;
// 👆プロパティに代入しているかに見えますが、setterメソッドを呼び出しています。
console.log(product.price); // 実行結果: 1200
product.price = -500; // 実行結果: Price must be positive.
コードの補足解説
プロパティ名にアンダースコア(_)を付ける主な理由は、アクセサメソッドの名前と区別するためです。
JavaScriptの命名規則では、同じ名前のプロパティとメソッドを区別するためにアンダースコアを使うのが一般的です。
極論、アンダースコアを付加せずとも、エラーもなく正常に動作します。
しかし、可読性や慣例を考慮し、アクセサメソッドを使用する際には命名規則に準じるのが望ましいです。
constructor(name, price) {
this._name = name;
this._price = price;
}
静的メソッドと静的プロパティ(static)
JavaScriptのclassでは、staticキーワードを使用して静的メソッドや静的プロパティを定義できます。
インスタンスを作成せずclassから直接呼び出せるメソッドを「静的メソッド」、直接参照できるプロパティを「静的プロパティ」と呼びます。
これにより、インスタンスによらずclass全体に関連するユーティリティ関数や、定数を定義するのに使用します。
静的メソッドの例
class Calculator {
static add(a, b) {
return a + b;
}
}
// 👇インスタンスを生成することなく、classから直接addメソッドを呼び出せている
console.log( Calculator.add(5, 3) ); // 実行結果: 8
静的プロパティの例
class Settings {
static defaultColor = 'blue';
}
// 👇インスタンスを生成することなく、classから直接defaultColorプロパティを参照している
console.log(Settings.defaultColor); // 実行結果: blue
まとめ
classを使うことで、オブジェクト指向プログラミングの概念を活用し、コードの再利用性や保守性を向上させることができます。
また、近年重要度が増し続けているTypeScriptや、JavaScript以外のオブジェクト指向言語(Java、Pythonなど)、フレームワーク(Reactなど)を学ぶ際にも大変役立つ知識です。
今回紹介した基本的な使い方を元に、オリジナルのプログラムを作成するなどして、ぜひ試してみていただければと思います。
本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。
それでは最後までお読みいただき、ありがとうございました。

コメント