クラス
クラスとインスタンス
オブジェクトを使うと、複数の値をひとまとまりに扱うことができました。実世界においては、同じプロパティ(属性)を持つオブジェクトを多く扱う場合が多いです。例えば、学生をオブジェクトとして表すことを考えてみましょう。学生には必ず名前と年齢という属性があるはずなので、ひとまず name
と age
をプロパティに持つとしましょう。
const tanaka = {
name: "田中",
age: 18,
};
同じ属性を持つオブジェクトを複数生成するときに役立つのがクラスです。クラスでは、オブジェクトのプロパティを予め設定しておくだけでなく、下のメソッドの節で説明するように、プロパティを引数にもつような関数も設定しておくことができます。これにより、同じコードを何度も書く必要がなくなるというメリットがあります。クラスは、同じプロパティを持つオブジェクトを統一的に扱うための仕組みであり、オブジェクトの設計図と言えます。
次のコードでは、先ほど作った tanaka
のように name
や age
というプロパティを持つオブジェクトの設計図として、クラス Student
を定義しています。クラスでは、この例の age
プロパティのように、デフォルトの値を設定することができます。
class Student {
name; // name プロパティを作成する
age = 18; // age プロパティのデフォルト値として `18` を使用する
}
クラスの名前は、通常のキャメルケースの最初の文字を大文字にしたパスカルケースで記述するのが普通です。
new
演算子をクラスに対して適用すると、設計図に基づいてオブジェクトが作成されます。こうしてできたオブジェクトを、もとになったクラスのインスタンスと呼びます。今回の age
プロパティのように、クラスのプロパティにデフォルトの値が設定されている場合、新たな値を代入するまではデフォルト値が入ります。もちろん、プロパティに新たな値を代入してデフォルト値を書き換えることもできます。
const tanaka = new Student(); // Student クラスをもとにオブジェクトを作成する
tanaka.name = "田中"; // name プロパティに代入
document.write(tanaka.age); // age プロパティのデフォルト値は 18
undefined
という値上で定義した Student
クラスには、デフォルト値の指定されていないプロパティ name
が存在します。new Student
をした直後のオブジェクトの name
プロパティの値はどうなっているのでしょうか。
実は、JavaScript には、未定義であることを表す特殊な値 undefined
が存在しています。これまで、JavaScript の値には数値、文字列、論理値、オブジェクトがあるとしてきましたが、こ れらとはまた別の値です。
存在しないプロパティの値、値を返さない関数の戻り値などは、すべて undefined
となります。
const emptyObject = {};
function emptyFunction() {}
document.write(emptyObject.unknownProperty); // 存在しないプロパティは undefined
document.write(emptyFunction()); // 値を返さない関数の戻り値は undefined
課題
weightInTons
と cost
をプロパティとして持ち、 weightInTons
のデフォルト値が 1
であるクラス Car
を作成し、 cost
に好きな値を代入してみましょう。
メソッド
同じプロパティを持つオブジェクトに対しては、同じような処理を行うことが多いです。例えば、学生はたいてい最初の授業で自己紹介をします。そこで、 Student
クラスに、自己紹介をする関数 introduceSelf()
を設定してみましょう。
オブジェクトに対して定義されている関数をメソッドと呼びます。メソッドの定義はクラス定義の中で行われますが、関数と異なり、function
キーワードを必要としません。
class Student {
name;
age;
// メソッド introduceSelf を定義する
introduceSelf() {
// this は作成されたインスタンスを指す
document.write(`私の名前は${this.name}です。${this.age}歳です。`);
}
}
クラス自体は単なる設計図でしかないため、実際のオブジェクトが存在するわけではありません。
そこで、メソッド内では、設計図から作成されたインスタンス自身を指す特殊な変数 this
が使用できます。
メソッドを使用するには、プロパティへのアクセス時と同じく、インスタンスに対して .
(ドット)記号を用います。
const tanaka = new Student();
tanaka.name = "田中";
tanaka.age = 18;
// introduceSelf メソッド内では this は tanaka に格納されたオブジェクトになる
tanaka.introduceSelf();
prototype
多くの言語で、クラス Class
のメソッドやプロパティ method
を、#
記号を用いて Class#method
と表記します。本資料では他言語の慣習に習い、この表記を用いるものとします。たとえば、上の例で定義されているメソッドは Student#introduceSelf
メソッドです。
ただし、JavaScript においては prototype
という語を用いて Class.prototype.method
とされる場合があります。これはより厳密な表記です。外部の資料を読む場合は注意してください。
課題
自分自身の年齢を 1 増やすメソッド incrementAge
を定義して、実行してみてください。
解答例: 年齢を増やすメソッド
class Student {
name;
age = 18;
introduceSelf() {
document.write(`私の名前は${this.name}です。`);
document.write(`${this.age}歳です`);
}
incrementAge() {
this.age += 1;
}
}
const tanaka = new Student();
tanaka.name = "田中";
tanaka.age = 19;
tanaka.introduceSelf();
tanaka.incrementAge();
tanaka.introduceSelf();
コンストラクタ
コンストラクタは、インスタンスを作成するタイミング(new
演算子をクラスに適用するタイミング)で実行される特殊なメソッドです。コンストラクタとなるメソッドは constructor
という名前で定義する必要があります。コンストラクタを定義すると、new Student
を実行してインスタンスを生成するときにプロパティの設定も同時に行うことができます。
class Student {
name;
age;
// コンストラクタを定義する
constructor(name, birthYear, currentYear) {
// this.name は作成され たインスタンスのプロパティ
// name はコンストラクタに渡された引数
this.name = name;
this.age = currentYear - birthYear;
}
introduceSelf() {
document.write(`私の名前は${this.name}です。${this.age}歳です。`);
}
}
const tanaka = new Student("田中", 2004, 2022);
tanaka.introduceSelf();
クラスとコンストラクタのメリットを理解するために、クラスのインスタンスを複数生成する場合を考えましょう。 例えば、田中さん、鈴木さん、佐藤さんが続けて自己紹介する場合、クラスを使わないでコードを書くと以下のようになります。
const tanaka = {
name: "田中",
age: 18,
introduceSelf() {
document.write(`<p>私の名前は${tanaka.name}です。${tanaka.age}歳です。<p>`);
},
};
const suzuki = {
name: "鈴木",
age: 20,
introduceSelf() {
document.write(`<p>私の名前は${suzuki.name}です。${suzuki.age}歳です。<p>`);
},
};
const sato = {
name: "佐藤",
age: 20,
introduceSelf() {
document.write(`<p>私の名前は${sato.name}です。${sato.age}歳です。<p>`);
},
};
tanaka.introduceSelf();
suzuki.introduceSelf();
sato.introduceSelf();
オブジェクトの定義が長くなり、書くのも読むのも大変です。さらに人数が増えると、コードはどんどん長くなってしまいます。また、introduceSelf()
関数の定義はほとんど同じコードが 3 回繰り返されています。
では、クラスとコンストラクタを用いるとどうでしょうか。
class Student {
name;
age;
// コンストラクタを定義する
constructor(name, age) {
// this は作成されたインスタンスを指す
this.name = name;
this.age = age;
}
// メソッド introduceSelf を定義する
introduceSelf() {
document.write(`<p>私の名前は${this.name}です。${this.age}歳です。<p>`);
}
}
const tanaka = new Student("田中", 18);
const suzuki = new Student("鈴木", 20);
const sato = new Student("佐藤", 20);
tanaka.introduceSelf();
suzuki.introduceSelf();
sato.introduceSelf();
クラスの定義自体はやや長いものの、1 つのオブジェクトの定義はたった 1 行で済みます。これならオブジェクトの数が増えても安心です。introduceSelf()
関数の定義を繰り返す必要もなくなり、読みやすく編集しやすいコードになりました。
継承
クラス定義の際に extends
キーワードを用いて別のクラスを指定すると、指定された