var Fruit = function() { };上記のfunctionは、new で Fruitクラスを生成するときに実行するものです。 その意味では、コンストラクタと呼んでもよいでしょう。
var apple = new Fruit(); alert(apple);以下では引数があるコンストラクタとプロパティ、メソッドを用意した例です。
var Fruit = function(name,price) {
this.name=name;
this.price=price;
this.toString=function(){
return this.name+"は"+this.price+"円";
};
};
var apple=new Fruit("りんご", 130);
apple.alert=function(){
alert(this.toString());
};
これで、apple.alert();と実行できます。
しかし
インスタンスに追加したメンバは、オブジェクト共通ではなく
そのインスタンスのみで有効です。mikan=new Fruit("みかん", 50); mikan.alert();
つまり、インスタンス共通のメソッドを定義するには、インスタンスに対してではなく、
コンストラクタ内で定義する必要があるというわけです。
こうすると、インスタンス内の各メソッドごとに、メソッド参照するための記憶領域が用意されことが分かります。
(これにより、特定のインスタンスのメソッドの置き換えを可能にしています。)
上記例で示したようにコンストラクタ内で作ったtoStringやalertの関数を参照する変数は、
インスタンスごとにその記憶域が存在します。
これは、メモリの節約から考えると 良い方法ではないでしょう。
さて、それを回避する方法がprototypeを使った
関数定義で、その例を以下に示します。
var Fruit = function(name,price) { this.name=name; this.price=price; }; Fruit.prototype.toString=function(){ return this.name+"は"+this.price+"円"; }; Fruit.prototype.alert=function(){ alert(this.toString()); };
この場合、各インスタンスは、共通のprototypeという領域を参照することで
toStringや、alertメソッドが実行されます。
この、各インスタンス共通のprototypeを使うことから、
メモリの節約もできるという訳です。
なお、先に述べたインスタンス内に関数を参照する変数も使えます。
この変数に関数を割り当てた場合と、prototypeの関数の両方を持つ場合は、
どちらが動作するのでしょうか?
次のような例の場合です。
var Fruit = function() { this.alert=function(){ alert("ミカン"); }; }; Fruit.prototype.alert=function(){ alert("りんご"); }; var a=new Fruit(); var b=new Fruit(); delete b.alert; //bのalert変数を削除(prototypeのalertは残っている) a.alert(); //ミカンの表示 b.alert(); //りんごの表示
上記のa.alert()の実行は、コメントで示したミカンが表示される。つまりインスタンス内の変数が優先されるということです。
上記で使っている delete命令はオブジェクトを削除する命令です。
delete b.alertよって、インスタンス内の変数が無くなることによりprototype.alertが実行されて、りんごの表示がでます。
以上より分かるように、実行する関数が検索される順番は、インスタンス内の関数変数、次にprototypeの関数変数ということにです。
なお delete演算子ではなく、インスタンス側のプロパティにundefined(未定義)値を設定するとどうなるでしょうか?
上記の delete b.alert; を b.alert= undefined; とすると
実行エラーが起きるでしょう。この場合はインスタンス内の関数変数が削除されたのではなく、存在したまま記憶内容が未定義になり、
未定義の関数を実行したためにエラーが発生しているのです。
なお、上記でりprototypeに関数を参照する変数を指定した例を示していますが、これは単なる情報を記憶した場合の動作も同じです。
以下に例を示します。
var Fruit = function() { this.data="ミカン"; }; Fruit.prototype.data="りんご"; var a=new Fruit(); var b=new Fruit(); delete b.data; //bのdata変数を削除(prototypeのdataは残っている) alert(a.data); //ミカンの表示 alert(b.data); //りんごの表示
リテラルとは、任意の式内で直接に記述可能なデータ表現のことです。
JavaScriptでは、連想配列(ハッシュ)のオブジェクト・リテラルの表現方法で、
{名前1 : 値1,名前2 : 値2,名前3 : 値3, ……}
という表記があります。
{ と } で 変数とその設定値を挟みます。この時、変数と設定値の間に:を付けます。
そして,で区切って並べる表現です。
この表現を利用して、prototypeに関数を設定する例を示します。
var Fruit = function(name,price) { this.name=name; this.price=price; }; Fruit.prototype={ toString:function(){ return this.name+"は"+this.price+"円"; }, alert:function(){ alert(this.toString()); } };
以下で、Fruitを継承したSpecialFruitのインスタンスの動作例を示します。
var Fruit = function(name,price) { this.name=name; this.price=price; }; Fruit.prototype={ toString:function(){ return this.name+"は"+this.price+"円"; }, alert:function(){ alert(this.toString()); } }; var SpecialFruit = function() {}; SpecialFruit.prototype=new Fruit("青森りんご",180); var apple=new SpecialFruit(); apple.alert();//「青森りんごは180円」の表示 SpecialFruit.prototype.toString=function(){ return this.price+"円で"+this.name+"を買えます。"; }; apple.alert();//「180円で、青森りんごが買えます。」の表示
プロトタイプ・オブジェクト(SpecialFruit.prototype)にFruitクラスのインスタンスを格納しているという点に
注目してください。
これによって、SpecialFruitクラスのインスタンスでは、Fruitクラスのalertメソッドを呼び出すことが可能です。
そして、動的に振る舞いを変更することができます。
上記の例では、途中でtoStringのメソッドを変更することで、alertの動作を変更しています。
このように、JavaScriptではプロトタイプにインスタンスを設定することで、インスタンス間の継承関係を形成することができます。
もちろん、この継承関係はさらに多階層にすることも可能で、その場合にも順に階層をさかのぼって、
最上位のObject.prototypeに行き当たるまでメンバの検索が行われることになります。
補足説明
上記もオブブジェクト・リテラルでprototypeの設定例を示しました。
実はJavaScriptにおいて、オブジェクトと連想配列との間に厳密な区別はありません。
つまり、オブジェクトと連想配列は、JavaScriptの世界においては同一の概念なのです。
よって次の左右の記述は、意味的に完全に同じです。
var obj = new Object(); obj.x = 1; obj.y = 2; |
var obj = {x:1, y:2}; |
上記において、obj.xの表現は、連想配列のアクセス方法で行うと
obj["x"]となり、両者は同じことです。
なお、コンストラクタを用意しなくても、簡単なインスタンスであれば、
オブブジェクト・リテラルの表現で生成できます。
その例を以下に示します。
var apple = { name: "りんご", price: 130, alert: function() { alert(this.name+"は"+this.price+"円"); } }; apple["alert"](); //apple.alert(); と同じです。