はじめに
JavaScriptのクラスは、オブジェクト指向プログラミングを実現するための構文です。この記事では、クラスの基礎から継承まで、実例を交えながら解説していきます。
クラスの背景にはプロトタイプという仕組みがあります。この仕組みを理解することで、クラスの動作をより深く把握できるようになりますので、順を追って見ていきましょう。
クラスとは
クラスは、オブジェクトの設計図のような役割を持ちます。同じ構造を持つオブジェクトを複数作成したい場合に、クラスを定義しておくことで効率的に生成できます。
JavaScriptのクラスは、ES2015で導入された比較的新しい構文ですが、内部的には従来からあるプロトタイプの仕組みを使って実装されています。
プロトタイプの仕組みについては下記の記事を参考にしてください。
クラスの基本構造
クラス宣言
クラスはclassキーワードを使って宣言します。インスタンスを生成する際には、newキーワードを使います。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`こんにちは、${this.name}です。`);
}
}
// インスタンスの生成
const taro = new Person('太郎', 25);
taro.greet(); // こんにちは、太郎です。
プロトタイプとクラスの関係(初心者の方は読み飛ばしてOKです)
JavaScriptのクラスは、裏側でプロトタイプの仕組みを利用しています。クラス構文を使わずに、プロトタイプで同じことを実現すると以下のようになります。
// プロトタイプを使った従来の書き方
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`こんにちは、${this.name}です。`);
};
クラス構文は、このプロトタイプの書き方をより分かりやすくしたものと言えますね。
インスタンス
インスタンスとは、クラスやコンストラクタから生成されたオブジェクトのことです。
const taro = new Person('太郎', 25);
const hanako = new Person('花子', 23);
上記の例では、taroとhanakoがそれぞれPersonクラスのインスタンスです。各インスタンスは独立したデータを持ちますが、メソッドはプロトタイプを通じて共有されます。
console.log(taro.name); // 太郎
console.log(hanako.name); // 花子
// メソッドは共有されている
console.log(taro.greet === hanako.greet); // true
プロパティ
プロパティとは、オブジェクトが持つデータや機能のことです。プロパティには大きく分けて2種類あります。
インスタンスプロパティ
各インスタンスが個別に持つプロパティです。通常、constructorの中でthisを使って定義します。
class Person {
constructor(name, age) {
this.name = name; // インスタンスプロパティ
this.age = age; // インスタンスプロパティ
}
}
メソッド(プロトタイププロパティ)
クラスの中に直接定義した関数は、プロトタイプに設定されます。これにより、すべてのインスタンスが同じメソッドを共有できます。
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
}
const calc1 = new Calculator();
const calc2 = new Calculator();
// メソッドは共有されている(メモリ効率が良い)
console.log(calc1.add === calc2.add); // true
constructorメソッド
constructorは、クラスからインスタンスが生成されたときに自動的に実行される特別なメソッドです。主にインスタンスの初期化処理を記述します。
class Book {
constructor(title, author) {
console.log('新しい本が作成されました');
this.title = title;
this.author = author;
this.isRead = false;
}
markAsRead() {
this.isRead = true;
}
}
const book = new Book('JavaScript入門', '山田太郎');
// コンソールに「新しい本が作成されました」と表示される
constructorは1つのクラスに1つだけ定義できます。省略した場合は、空のconstructorが自動的に用意されます。
クラスメソッド
クラスの中に関数を定義すると、それはプロトタイプの中で定義されたメソッドになります。これらのメソッドは、オブジェクトごとに共通のユニークな関数として機能します。
class Counter {
constructor() {
this.count = 0;
}
// プロトタイプに定義されるメソッド
increment() {
this.count++;
}
decrement() {
this.count--;
}
getCount() {
return this.count;
}
}
const counter1 = new Counter();
const counter2 = new Counter();
counter1.increment();
counter1.increment();
console.log(counter1.getCount()); // 2
// counter2は独立している
console.log(counter2.getCount()); // 0
thisキーワード
クラスの中のthisは、そのクラスから生成されたインスタンス自身を指します。これにより、各インスタンスが自分自身のプロパティやメソッドにアクセスできます。
class BankAccount {
constructor(owner, balance) {
this.owner = owner;
this.balance = balance;
}
deposit(amount) {
this.balance += amount; // このインスタンスのbalanceを増やす
console.log(`${this.owner}さんの残高: ${this.balance}円`);
}
withdraw(amount) {
if (this.balance >= amount) {
this.balance -= amount;
console.log(`${this.owner}さんの残高: ${this.balance}円`);
}
}
}
const account1 = new BankAccount('田中', 10000);
const account2 = new BankAccount('佐藤', 5000);
account1.deposit(3000); // 田中さんの残高: 13000円
account2.deposit(2000); // 佐藤さんの残高: 7000円
それぞれのインスタンスでthisは異なるオブジェクトを指すため、独立したデータ管理が可能になります。
継承
継承を使うと、複数のクラスで共通の機能を親クラスとして定義し、それを受け継ぐことができます。コードの再利用性が高まり、保守性も向上します。
extendsによる継承
extendsキーワードを使って親クラスを継承できます。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}が鳴いています`);
}
}
class Dog extends Animal {
bark() {
console.log(`${this.name}: ワンワン!`);
}
}
const dog = new Dog('ポチ');
dog.speak(); // ポチが鳴いています
dog.bark(); // ポチ: ワンワン!
プロパティとメソッドのオーバーライド
親と子で共通のプロパティやメソッドがある場合、子のものが優先されます。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}が鳴いています`);
}
}
class Cat extends Animal {
// メソッドのオーバーライド
speak() {
console.log(`${this.name}: ニャー`);
}
}
const cat = new Cat('タマ');
cat.speak(); // タマ: ニャー(親のspeakではなく、子のspeakが実行される)
superキーワード
superを使うことで、親クラスの機能を参照しつつ、子クラスでも独自のロジックを追加できます。
class Animal {
constructor(name) {
this.name = name;
}
introduce() {
console.log(`名前は${this.name}です`);
}
}
class Bird extends Animal {
constructor(name, canFly) {
super(name); // 親クラスのconstructorを呼び出す
this.canFly = canFly;
}
introduce() {
super.introduce(); // 親クラスのintroduceを呼び出す
if (this.canFly) {
console.log('空を飛べます');
} else {
console.log('空は飛べません');
}
}
}
const penguin = new Bird('ペンギン', false);
penguin.introduce();
// 名前はペンギンです
// 空は飛べません
super()をconstructorで使う場合は、thisを使う前に呼び出す必要があります。これは親クラスの初期化が先に必要なためです。
まとめ
この記事では、JavaScriptのクラスについて以下の内容を学びました。
- クラスは内部的にプロトタイプの仕組みで動作している
- インスタンスはクラスから生成されたオブジェクト
- constructorでインスタンスの初期化を行う
- クラスメソッドはプロトタイプに定義され、インスタンス間で共有される
- thisは生成されたインスタンス自身を指す
- extendsで継承を行い、superで親クラスの機能を利用できる
クラスを使うことで、オブジェクト指向プログラミングの考え方に基づいた、保守性の高いコードを書くことができます。実際のプロジェクトでも積極的に活用してみてください。