「オブジェクト指向とかクラスとか分からない単語を分からないままにしている」
「変数とインスタンス変数の違いが分からない」
「日付取得でnew Date( )としているコードを見るけど何をしているか分からずコピペしている」
本日はそんな方に向けてクラスとインスタンスについて解説していきます。
どんな言語からプログラミング学習を始めても必ず学習するのですが、初学者の方にはなかなか難しい考え方だと思います。
今回はJavaScriptを題材にしてクラスとインスタンスについて「これだけは知っておきたい」と言うものに絞ってまとめてみました。
動画もあるので挫折しかけた人も今一度やり直してみてください。
クラスとインスタンスとはなぜセットなのか?
いきなりですがクラスの存在理由については最初は知らなくても大丈夫です。
「クラスとは?」から入ると一気に理解するハードルが上がってしまうためです。
本記事でもクラスとは「変数やメソッドが入ったテンプレート」と言う説明から始めます。
JavaScriptについてはクラスを以下のような書き方でクラスを作ります。
class Test {
// コンストラクターの部分
constructor() {
console.log("constructor");
}
}
classと続けて自分でクラスの名前をつけることができて、今回は「Test」としています。
最初の文字は大文字にするのがルールになっていて、他の人が見てもわかりやすい名前をつけるようにします。
クラスには必ずコンストラクターが必要になり、コンストラクターの部分には「クラスが実行されて最初に行う処理」を書きます。
またクラスは上記のように書いただけでは実行できません。
クラスを実行したいときは以下のようにします。
class Test {
constructor() {
console.log("constructor");
}
}
// ここを追加
new Test(); // 「constructor」と出力される
作ったクラスはnewに続けてクラス名と( )を書くことで実行できます。
今回はコンソールに「constructor」と出力されます。
このnew Test( )のようにすることを「インスタンス化(クラスの実行)」と呼びます。
専門用語が多いだけで文法だけ見ると関数と似ているかと思います。
例えばコンストラクターですが関数と同じように引数を渡すことができます。
class Test {
constructor(val) {
console.log(val);
}
}
// ここを追加
new Test(10); // 「10」と出力される
関数と似ていると分かると少しは理解が進むのではないでしょうか。
またnew Test( )は変数に格納することもできます。
class Test {
constructor() {
console.log("constructor");
}
}
// ここを修正
let test = new Test(); // 「constructor」と出力される
変数testにnew Test( )を格納しました。
クラスのインスタンス化が格納してありますが処理としては変数を作っただけです。
冒頭でクラスは「変数やメソッドが入ったテンプレート」と言いましたが、テンプレートなのでいろんな場所で使い回すことを想定してクラスというのは作ることが多いです。
そのため普通にインスタンス化するだけでなく、変数に格納することが多くなるでしょう。
そのためクラスとインスタンスはセットで紹介されるのです。
クラスの中でのインスタンス変数について
クラスの中で設定した変数はインスタンス変数と呼びます。
インスタンス変数は変数と同じような役割だと思ってもらって大丈夫ですが書き方が少し違います。
class Test {
// ここを変更
constructor() {
this.author = "山田";
}
}
let test = new Test();
console.log(test.author); // 「山田」と出力される
上記のコードではコンストラクターの中でインスタンス変数を設定してみました。
インスタンス変数はthisに続けて名前を設定するルールになっています。
インスタンス変数を使うときはクラスをインスタンス化して「クラス名 . 変数名」で使用できます。
インスタンス変数には引数を渡すこともできます。
class Test {
// ここを修正
constructor(val) {
this.author = val;
}
}
// ここを修正
let test = new Test("田中");
console.log(test.author); // 「田中」と出力される
クラスの中でのメソッドについて
変数に続いてメソッドと言うものを紹介していきます。
厳密には関数とは違うものですがイメージしてもらうために「メソッド=関数みたいなもの」と言う理解で十分でしょう。
メソッドは以下のように書きます。
class Test {
constructor() {
console.log("初期化");
}
// メソッドの部分
hello() {
console.log("hello");
return "aaa";
}
}
let test = new Test();
console.log(test.hello());
出力内容が複数ありますね、まず最初の出力内容はコンストラクターによるものです。
2行目と3行目がメソッドであるhello( )の出力内容です。
メソッドは関数と同じようなものと言いましたが、メソッドにもreturnとして戻り値を設定することができます。
returnを設定しなかった場合は関数と同じく「戻り値=undefined」になります。
さらにメソッドでは関数と同じく変数との掛け合わせも可能です。
class Test {
constructor() {
console.log("初期化");
// ここを追加
this.author = "山田";
}
hello() {
console.log("hello");
// ここを変更
return this.author;
}
}
let test = new Test();
console.log(test.hello());
メソッドであるhello( )の戻り値をインスタンス変数に設定したので、this.author = “山田”である「山田」が出力されていますね。
ここまでは関数と同じような感覚を持ってもらって大丈夫なのですが、クラスの場合は「静的メソッド」と言う特殊なメソッドも覚えておく必要があります。
class Test {
constructor() {
console.log("初期化");
this.author = "山田";
}
hello() {
console.log("hello");
return this.author;
}
// 静的メソッドの部分
static staticHello() {
console.log("static");
}
}
Test.staticHello(); // 「static」と出力される
staticHello( )と言う部分が静的メソッドになっていて、まずstaticに続けて名前をつけるのが静的メソッドのルールです。
続いて静的メソッドの特徴としては、「インスタンス化してなくても実行できる」と言うものです。
上記のコードのように「クラス名 . 静的メソッド名」とするだけで実行できます。
ここまで読んできた中で「じゃあ静的メソッドだけで書けば良いのでは?」と思われた方がいるかもしれません。
確かに静的メソッドはコードがシンプルなので便利なのですが、インスタンス変数やメソッドは結局インスタンス化しないと使用できないのです。
どういうことかと言うと以下のようにコードを作り替えてみます。
class Test {
constructor() {
console.log("初期化");
this.author = "山田";
}
hello() {
console.log("hello");
return this.author;
}
// ここを修正
static staticHello() {
console.log(this.author);
}
}
Test.staticHello(); // 「undefined」と出力される
静的メソッドであるstaticHello( )の戻り値をインスタンス変数であるthis.authorにしました。
すると「山田」は出力されずundefinedとなってしまいます。
staticHello( )は実行できてもthis.authorはインスタンス化されていない以上は呼び出すことができないからです。
このように静的メソッドで全てのメソッドを作るのは基本的には不可能と言えます。
静的メソッドが効果を発揮するのは、インスタンス変数や他のメソッドを呼ばない処理です。
初学者の方が慣れないうちは、メソッドを作っている途中で「このメソッドはインスタンス変数や他のメソッドを読んでないな」と気付いた時にでも静的メソッドに変更するのが良いでしょう。
クラスの継承について
同じ案件でクラスを複数作るなんてことがあります。
基本的にはクラスは個別にインスタンス変数やメソッドを作るのですが、状況によっては「他のクラスと同じ内容になるな」と言う場面があります。
そんな時にはわざわざ同じコードを2回書かなくても良いように、「クラスの継承」と言う仕組みが用意されています。
class Test {
constructor() {
console.log("初期化");
this.author = "山田";
}
hello() {
console.log("hello");
return this.author;
}
}
// Testクラスを継承してCoptTestと言うクラスを作成
class CopyTest extends Test {
constructor() {
super();
}
copyHello() {
super.hello();
}
}
let copy = new CopyTest();
console.log(copy.copyHello());
JavaScriptの場合だとクラス名に続けて「extends 継承したいクラス名」と書くと、他のクラスの内容を引き継ぐことができます。
さらに他のクラスを継承した場合は「super」と書くことでコンストラクターやメソッドをコピーすることができます。
上記のコードではコンストラクターとメソッドのhello( )をコピーしたcopyHello( )と言うメソッドを新たに作成しています。
しかし元あったメソッドhello( )の戻り値が引き継げていないことが分かります。
クラスを継承した際の注意点として「this」の扱いがあるからです。
元のメソッドhello( )の戻り値はthis.authorなので「山田」が入っていても良さそうですが、残念ながら「this」はクラスのことを指すため継承したクラスのCopyTestでは呼ぶことができていません。
コンソールログでthisを確認してみましょう。
〜省略〜
class CopyTest extends Test {
constructor() {
super();
// ここを追加
console.log(this);
}
copyHello() {
super.hello();
}
}
〜省略〜
thisをコンソールで表示するとクラス名が表示され、今回だとCopyTestになっています。
CopyTestの中にはTestから引き継いだインスタンス変数のauthorがあります。
このauthorを使いたい場合にはCopyTestの中で再度this.authorとして呼ばないといけません。
元のクラスTestのhello( )の中でも「return this.author」と言う記述がありますが、こちらのthisはあくまで元のクラスであるTestを指しています。
そのためメソッドhello( )を引き継いだとしても「this.author」の中身が元のクラスのままなので、CopyTestで実行してもundefined(見つかりません)となるわけです。
〜省略〜
class CopyTest extends Test {
constructor() {
super();
}
copyHello() {
super.hello();
// ここを追加
return this.author;
}
}
〜省略〜
copyHello( )の戻り値にthis.authorを設定したことで戻り値がちゃんと出力されました。
継承は便利ですがthisの中身が何になるかは細かくコンソールログで確認するようにしましょう。
クラスとインスタンスについてはまだまだ知らないといけないことがありますが、初学者がイメージするうえでは今回の内容だけ理解すれば大丈夫です。
ちなみに本記事ではクラスの作成について説明していましたが、実際には「誰かが作ったクラスを利用する」ことも多いです。
JavaScriptを勉強している中で日付の取得を以下のようなコードで習ったかと思います。
let now = new Date();
console.log(now.getFullYear()); // 「2023」と出力される
現在の日付データを取得する簡単なコードですが、よくよく思い返してみるとこちらもクラスをインスタンス化していますね。
初学者のうちは学習教材やググった記事からコードを真似するだけになるわけですが、知識が増えていくと真似していたコードが「何をしているか?」が分かるようになってきます。
上記の例のようにJavaScriptでは便利なクラスを事前に用意してくれていて、私たちは自分のコードでインスタンス化するだけで簡単に実現したいものを作れるようになっています。
そのため本記事で紹介したようなクラスの作成だけでなく、「すでに用意されているクラスを使えないか?」と言うような考え方も持っておきましょう。