はじめに
プロトタイプとは簡単に言えば「メモリの節約に役立つ、JavaScript固有の継承の仕組み」です。
JavaScript独自の概念で難しそうに感じますが、使ってみると意外と簡単。
まずは書いて全体像を眺めてみましょう。
サンプルを書いてみよう
プロトタイプを使用するためのステップは、大きく分けて以下の3つになります。
1. コンストラクタでオブジェクトを定義 (関数オブジェクト)
2. インスタンスを生成 (new演算子でオブジェクトを生成)
3. コンストラクタのprototypeプロパティにメソッドを追加 (prototype拡張)
まずはステップに沿って書いてみましょう。
// 1. コンストラクタでオブジェクトを定義
var Person = function(name) {
this.name = name; // プロパティを定義
this.sayHello = function(){ // メソッドを定義
console.log('こんにちは! 私の名前は ' + this.name + ' です。'); // 処理
}
};
// 2. インスタンスを生成 (new演算子でオブジェクトを生成)
// Person というコンストラクタからインスタンスを生成
var taro = new Person('taro');
var jiro = new Person('jiro');
// メソッドを呼び出す (コンストラクタからコピーしたメソッド -> メモリを消耗する)
taro.sayHello(); // -> こんにちは! 私の名前は taro です。
jiro.sayHello(); // -> こんにちは! 私の名前は jiro です。
// 3. コンストラクタのprototypeプロパティにメソッドを追加
Person.prototype.dash = function(){ // メソッドを追加
console.log(this.name + ' は走った!'); // 処理
}
// 追加したメソッドを呼び出す (prototypeプロパティに追加したメソッドを参照 -> メモリを消耗しない)
taro.dash(); // -> taro は走った!
jiro.dash(); // -> jiro は走った!
それでは以降で、定義や生成の方法と照らし合わせて見ていきましょう。
1. コンストラクタでオブジェクトを定義
var コンストラクタ名 = function(引数) {
this.プロパティ名 = 値; // プロパティを定義
this.メソッド名 = function(){ // メソッドを定義
// 処理
};
};
// 1. コンストラクタでオブジェクトを定義
var Person = function(name) {
this.name = name; // プロパティを定義
this.sayHello = function(){ // メソッドを定義
console.log('こんにちは! 私の名前は ' + this.name + ' です。'); // 処理
}
};
コンストラクタを定義
コンストラクタとは、インスタンスを生成するために必要な、設計書となるオブジェクトを定義する関数です。
定義の方法は関数と似ていますね。
JavaScriptでは関数もオブジェクトの一種です(関数オブジェクト)。
このようにオブジェクトを定義するための関数をコンストラクタ
やコンストラクタ関数
と呼びます。
コンストラクタ名の慣例
コンストラクタ名は、他の関数と区別する目的で
最初の一文字を大文字にしておく慣例
があります。
プロパティとメソッドの定義
オブジェクト内で定義した値
と処理
のことを、それぞれプロパティ
とメソッド
と呼びます。
用語 | 説明 |
---|---|
プロパティ | オブジェクト内に定義した 値 |
メソッド | オブジェクト内に定義した 処理 |
this
thisとは「記述場所によって参照先の変わる特別な変数」です。
コンストラクタ内のthisはそのオブジェクト自身
を示し、プロパティとメソッドの定義は「this.プロパティ名
」「this.メソッド名
」のように書きます。
2. インスタンスを生成 (new演算子でオブジェクトを生成)
// インスタンスを生成 (new演算子でオブジェクトを生成)
var インスタンス名 = new オブジェクト名();
// メソッドを呼び出す
インスタンス名.メソッド名();
// 2. インスタンスを生成 (new演算子でオブジェクトを生成)
// Person というコンストラクタからインスタンスを生成
var taro = new Person('taro');
var jiro = new Person('jiro');
// メソッドを呼び出す (コンストラクタからコピーしたメソッド -> メモリを消耗する)
taro.sayHello(); // -> こんにちは! 私の名前は taro です。
jiro.sayHello(); // -> こんにちは! 私の名前は jiro です。
インスタンス
インスタンスとは、コンストラクタ(設計書)を元に作られたオブジェクト(実体)です。
車の製造に置き換えて考えてみればイメージしやすいと思います。
車の製造では | |
---|---|
コンストラクタ | 車の設計書 |
インスタンス | 組立てた車(実体・実物) |
コンストラクタで定義したオブジェクトは、new演算子
で呼び出すことができます。
「var インスタンス名 = new オブジェクト名();
」のように呼び出し、インスタンスを生成します。
new演算子
new演算子
は新しいオブジェクトを作るときに使います。「
new Array
」と書けば新しいArrayオブジェクト(配列)が作れるように、
「new コンストラクタ名();
」と書けば、新しいオブジェクト(インスタンス)を作れます。
メソッドの呼び出し
コンストラクタで定義したメソッドは「インスタンス名.メソッド名();
」で呼び出せます。
3. コンストラクタのprototypeプロパティにメソッドを追加
関数オブジェクトであるコンストラクタは、作成した時点でprototypeというプロパティを持っています。
このprototypeプロパティを利用すると、既に作成済みのコンストラクタであっても、後からメソッドを追加できます。
コンストラクタ名.prototype.メソッド名 = function(){
// 処理
}
// 3. コンストラクタのprototypeプロパティにメソッドを追加
Person.prototype.dash = function(){ // メソッドを追加
console.log(this.name + ' は走った!'); // 処理
}
// 追加したメソッドを呼び出す (prototypeプロパティに追加したメソッドを参照 -> メモリを消耗しない)
taro.dash(); // -> taro は走った!
jiro.dash(); // -> jiro は走った!
上記のように書くことで、コンストラクタに対してメソッドを追加できて、そのコンストラクタから既に生成されているインスタンスでも、最初から定義されていたかのように、追加したメソッドを共通利用することができるようになります。
このようなメソッドの追加はプロトタイプ拡張
呼ばれ、JavaScript固有の継承の仕組みになります。
追加したメソッドは参照 (コピーではない)
サンプルではコンストラクタに対して、以下の2種類の方法でメソッドを用意しています。
どちらの方法が良いのでしょうか?
メソッド用意の方法 | インスタンスから呼び出すメソッド | メモリの消費 | |
---|---|---|---|
1. | コンストラクタ内でメソッドを定義 | インスタンス自身が持つメソッドを呼び出す。 (インスタンス生成時に new()演算子 でコピーしたメソッド) |
消費する |
2. | prototype拡張でメソッドを追加 | prototypeプロパティに追加したメソッドを参照 | 消費しない |
答えは「2. prototype拡張でメソッドを定義」です。メモリの節約ができることがその理由です。
コンストラクタから生成したインスタンスは、
コンストラクタに定義されている全てのプロパティとメソッドをコピーして持っています。
そのため...
- 「1.
コンストラクタ内でメソッドを定義
」では、生成したすべてのインスタンスはそれぞれにコンストラクタと同じメソッドをコピーして持っている。
- 「2.
prototype拡張でメソッドを追加
」では、生成したすべてのインスタンスはメソッドを持っていない。
(メソッドはコンストラクタのprototypeプロパティを参照して呼び出している)
といった違いがあります。
prototype拡張を利用すると
...
インスタンスはそれぞれがメソッドを持たないので、生成するときに余計なメモリを消費しないためメモリの節約
になるというわけです。
また、メソッドはどのインスタンスでも共通して使うものなので、コピーではなく参照の方が適しています。
サンプルのコンストラクタで定義しているメソッドは1つですが、もし20個のメソッドを定義して、そこからさらに大量のインスタンスの生成を考えればメモリの節約についても想像しやすいかと思います。
複数のメソッドを追加するパターン
プロトタイプ拡張で複数のメソッドを追加する方法は2種類あります。
下記はメソッドを定義しない空のコンストラクタを定義し、プロトタイプ拡張で複数のメソッドの追加するサンプルです。
パターン1 : prototype拡張を複数書く
// 空のコンストラクタを作成
var Sample1 = function(){}
// prototype拡張を複数書く
Sample1.prototype.method1 = function(){
console.log('method1...');
}
Sample1.prototype.method2 = function(){
console.log('method2...');
}
// インスタンスを作成
var test1 = new Sample1();
var test2 = new Sample1();
// メソッドを呼び出す
test1.method1(); // -> method1...
test2.method2(); // -> method2...
パターン2 : ひとつのprototypeにまとめて書く
// 空のコンストラクタを作成
var Sample2 = function(){}
// ひとつのprototypeにまとめて書く
Sample2.prototype = {
method3: function(){
console.log('method3...');
},
method4: function(){
console.log('method4...');
}
}
// インスタンスを作成
var test3 = new Sample2();
var test4 = new Sample2();
// メソッドを呼び出す
test3.method3(); // -> method3...
test4.method4(); // -> method4...
コンストラクタ自体は何も定義していない空の関数オブジェクトですが、prototypeプロパティにメソッドを追加することで、インスタンスでメソッドを共通利用できるようになりました。