こんにちは、ライターのマサトです!
今回は、JavaScriptで「継承」を実現する仕組みでもある「プロトタイプ(prototype)」について学習をしていきましょう。「プロトタイプ(prototype)」を使うとメモリの無駄遣いを防ぐことができます。
この記事では、
などの基本的な内容から、実用的な使い方に関しても解説していきます。この記事で、「プロトタイプ(prototype)」をしっかり学習して自分のスキルアップを目指しましょう!
「prototype」とは?
それでは、まず最初に「プロトタイプ(prototype)」について基本的な知識から学習を進めていきましょう!
JavaScriptでは、すべてのオブジェクトが「プロトタイプ」をベースにして作られています。
言い方を変えれば「プロトタイプ」と呼ばれる最小テンプレートがあり、それをコピーして新しいオブジェクトを作るようなイメージです。
このプロトタイプは、オブジェクト同士の繋がりを保持する機能もあるため「継承」も簡単に行えるわけです。JavaScriptはプロトタイプベースのオブジェクト指向言語です。
これに対して、JavaやC++、C#のようにクラスベースのオブジェクト指向言語が一般的です。本記事では、JavaScriptの特徴であるプロトタイプの使い方から「継承」パターンまで学習できるように構成しているのでぜひ参考にしてみてください!
「prototype」の使い方
この章では、プロトタイプ(prototype)を使ったメソッドの定義について学習します。プロトタイプを使うケースと使わないケースとそれぞれの違いについて詳しく見ていきましょう!
基本的なコンストラクタの作成方法
まずプロトタイプの説明の前に、簡単な「コンストラクタ」からおさらいしましょう。コンストラクタはオブジェクトを作る際に初期化の記述を行う処理のことでしたね。
次のサンプルは、「名前」と「年齢」だけの情報を持ったユーザーオブジェクトの例です。
var User = function(name, age) { this.name = name; this.age = age; } var taro = new User('太郎', 30); console.log( taro );
実行結果
{"name":"太郎","age":30}
この例では、「User」というオブジェクトを作成して「taro」というインスタンスを生成しています。実行結果を見ると分かるように、コンストラクタで記述した初期化処理をベースにしたオブジェクト構造が生成されていますね。
まだよく分からないという方は、次の記事で基本から応用技までまとめているのでぜひ参考にしてみてください!
prototypeを使ってメソッドを定義する方法
すべてのオブジェクトは「プロトタイプ」を持っていると冒頭で解説しました。先ほど作成した「User」コンストラクタには、プロパティだけが記述されていました。メソッドも一緒に定義する場合、プロトタイプでメソッドを効率的に定義することができます。
プロトタイプの一般的な記述方法は、
【 オブジェクト名.prototype.メソッド名= function() { } 】
となります。
プロトタイプで記述すると、インスタンス化の際にメソッドは「参照」して利用されます!
コピーが作られるのではありません。
基本的にメソッドはどのインスタンス先でも内容は同じなので、一緒にコピーするのは無駄になります。例えば、複雑なメソッドを100個くらい持つオブジェクトで、数十個のインスタンスを生成するとメモリの圧迫は深刻です。大量のメソッドがあった場合、コピーだとインスタンス化する度にメモリが圧迫されます。
しかし「参照」であれば、大量のメソッドがあったとしてもインスタンス化する度にメモリが圧迫されることはありません。
次のサンプル例を見てください!
var User = function(name, age) { this.name = name; this.age = age; } User.prototype.getName = function() { return this.name; }
この例では、「User」オブジェクトのコンストラクタがプロパティの定義だけになっている点に注目してください。
そして、「prototype」プロパティを使って「getName()」メソッドを定義していますね。
prototypeを使わないでメソッドを定義する事例
ちなみに、prototypeを使わないでメソッドを定義する例をみてみましょう。
var User = function(name, age) { this.name = name; this.age = age; //メソッドを定義する this.getName = function() { return this.name; } }
この例では、Userオブジェクトに「getName()」という名前を取得するメソッドを定義しています。
しかしprototypeを使わないでメソッドを定義した場合、インスタンスを生成する度にメソッドも一緒にコピーされるので、無駄なメモリを消費してしまいます!
prototype定義の簡素化
さて、プロトタイプにメソッドを定義することでメモリを最適化できるようになりました。
しかし、このままだとメソッドの定義が増えるにつれて記述が複雑になり可読性やメンテナンスが難しくなってきます。
例えば、次のサンプル例を見てください!
var User = function(name, age) { this.name = name; this.age = age; } User.prototype.getName = function() { return this.name; } User.prototype.getAge = function() { return this.age; }
この例では、「getName()」「getAge()」という2つのメソッドをプロトタイプに定義しています。
ところが、メソッドの定義が30個〜50個と増えてきた場合に管理が難しくなると思いませんか?例えば、オブジェクト名が「User」から「Member」に変更することになったらすべてのメソッド定義に影響します。
そこで、プロトタイプに定義するメソッドをオブジェクト形式で記述することで1つにまとめる手法をご紹介します!
次のサンプル例を見てください!
User.prototype = { getName: function() { return this.name; }, getAge: function() { return this.age; } }
「User.prototype」に続けてオブジェクト形式でメソッドを定義しているのが分かりますね。
このように記述しても、最初の書き方とまったく同じことを実現できるわけです。この場合であれば、メソッド定義を1つにまとめられるうえオブジェクト名の変更も容易ですね!
「prototype」による継承
この章では、プロトタイプを活用した「継承」のしくみについて解説していきます!
重要な概念である「プロトタイプチェーン」や継承の作り方などを学んでいきましょう!
プロトタイプチェーンの仕組みについて
まずは、「プロトタイプチェーン」について解説していきます!
これは文字通り、各オブジェクトが持っている「プロトタイプ」をチェーンのように連結してお互いを参照できる仕組みになります。
例えば、次のサンプル例を見てください!
var User = function() {}; var MemberA = function() {}; var MemberB = function() {};
この例では、中身が空っぽのオブジェクトを3つ作成しています。
そこで、この3つのオブジェクトがそれぞれ持っている「プロトタイプ」を連結させるにはどうれば良いでしょうか?実は、各プロトタイプには連結させたいオブジェクトのインスタンスを代入することができるのです!
次の例を見てください!
MemberA.prototype = new User();
これは「MemberA」のプロトタイプに「User」オブジェクトのインスタンスを代入している例です。
このように記述することで、「User」と「MemberA」のプロトタイプはそれぞれ参照できる状態になります。
さらに、次のように記述してみましょう!
MemberB.prototype = new MemberA();
今度は、「MemberA」と「MemberB」のプロトタイプ同士を連結しました。これにより両者のオブジェクトは参照関係にあるのですが、「MemberB」と「User」も同じように参照関係になっています。
つまり、プロトタイプチェーンを利用することで「User / MemberA / MemberB」がそれぞれ繋がったわけです。今回のオブジェクトは中身が空っぽでしたが、通常なら「User」の機能を「MemberB」が利用することも可能なわけです。
prototypeを活用した継承の作り方
先ほどのプロトタイプチェーンが分かったら、JavaScriptの「継承」は簡単です!
と、言うのも「プロトタイプチェーン」が「継承」の仕組みそのものだと言っても過言ではありません。
次のサンプル例を見てください!
var User = function() {}; var Member = function() {}; User.prototype.hello = function() { return 'こんにちは!'; } Member.prototype = new User();
この例では、「User」「Member」2つの中身が無いオブジェクトを作成しています。
そして、Userオブジェクトのプロトタイプに「hello()」というメソッドを定義しています。この状態で「Member」のプロトタイプに「User」のインスタンスを代入するとどうなるでしょうか?
プロトタイプチェーンの章で解説した通り、「Member」オブジェクトでも「hello()」メソッドが利用できるようになりますよね。
例えば、次のような記述をしてみましょう!
var User = function() {}; var Member = function() {}; User.prototype.hello = function() { return 'こんにちは!'; } Member.prototype = new User(); var taro = new User(); var hanako = new Member(); console.log( taro.hello() ); console.log( hanako.hello() );
実行結果
こんにちは! こんにちは!
この例では、「User / Member」それぞれのインスタンスを作成しています。
注目して欲しいのは実行結果です。どちらも「hello()」メソッドが実行できているのが分かりますね。
つまり、プロトタイプチェーンによって「User」と「Member」が繋がることで「継承」を実現しているわけです。さらに「継承」について知識を深めたい方は、次の記事でさまざまな継承方法を解説しているので参考にしてみてください!
まとめ
今回は、JavaScriptのプロトタイプ(prototype)について学習をしました!
最後に、もう一度ポイントをおさらいしておきましょう!
- プロトタイプをベースにコピーをすることでオブジェクトを作成できる
- プロトタイプにメソッドを定義することでメモリ消費を抑制できる
- プロトタイプチェーンを利用することで継承を実現できる
上記内容を踏まえて、ぜひ自分でもプログラミングに取り入れて活用できるように頑張りましょう!