uhyohyo.net

JavaScript初級者から中級者になろう

九章第二回 prototypeの活用

このページの最終更新日:

今回は、prototypeというものを解説します。まず前回のサンプルを振り返ります。


var jikoshokai = function(){
  console.log("私の名前は" + this.name + "です。");
};

function Yuusha(n){
  this.name = n;
  this.jikoshokai = jikoshokai;
}

var yuusha1 = new Yuusha("勇者1");
var yuusha2 = new Yuusha("勇者2");
var yuusha3 = new Yuusha("勇者3");

yuusha1.jikoshokai();
yuusha2.jikoshokai();
yuusha3.jikoshokai();

前回これはまだ完璧ではないと述べました。今回の内容はそれと関連しています。

前回の内容で、勇者オブジェクトの初期化についてはいい感じにコンストラクタにまとめることができました。オブジェクト指向のもうひとつの特徴として紹介した共通の機能(メソッド)をまとめる方法を今回紹介します。それがprototypeです。

現状は何かよくわからないところにjikoshokai関数が定義されていて、一見して勇者オブジェクトの関係が分からないので微妙です。まず結論から述べると、よい書き方は次のような書き方です。


function Yuusha(n){
  this.name = n;
}

Yuusha.prototype.jikoshokai = function(){
  console.log("私の名前は" + this.name + "です。");
};

var yuusha1 = new Yuusha("勇者1");
var yuusha2 = new Yuusha("勇者2");
var yuusha3 = new Yuusha("勇者3");

yuusha1.jikoshokai();
yuusha2.jikoshokai();
yuusha3.jikoshokai();

実は、コンストラクタ(というより関数)はprototypeというプロパティを持っています。これはオブジェクトで、コンストラクタのprototypeが持つプロパティやメソッドは、インスタンスのプロパティやメソッドとして参照することができます

つまり、上の例ではYuushaのprototypeにjikoshokaiメソッドがあることにより、Yuushaのインスタンスであるyuusha1などのメソッドとしてjikoshokaiを使うことができたのです。

これの動作の仕組みは以下のようになっています。あるオブジェクトのプロパティやメソッドを使おうとしてもないとき、次にコンストラクタのprototypeを探すのです。それでもなければ、本当にないということで、以前述べたようにundefinedになります。

例えば、yuusha1.jikoshokaiの場合を見てみます。今回の場合、yuusha1のjikoshokaiというメソッドを探しますが、ありません。そこで、yuusha1のコンストラクタがYuushaなので、Yuusha.prototype.jikoshokaiを探します。そこであったので、無事自己紹介できたというわけです。

これを利用して、ちょっと遊んでみます。


function Yuusha(n){
  this.name = n;
}
Yuusha.prototype.jikoshokai = function(){
  console.log("私の名前は" + this.name + "です。" + "HPは" + this.hp + "です。");
};
Yuusha.prototype.hp = 100;

var yuusha1 = new Yuusha("勇者1");

//自己紹介してもらう
yuusha1.jikoshokai();

hpプロパティをprototypeで設定しました。これは、勇者(インスタンス)を何人作ってもhpは100ということですね。

次に、これはどうでしょう。


function Yuusha(n){
  this.name = n;
}
Yuusha.prototype.jikoshokai = function(){
  console.log("私の名前は" + this.name + "です。" + "HPは" + this.hp + "です。");
};
Yuusha.prototype.hp = 100;
//攻撃する
Yuusha.prototype.attack = function(){
  this.hp -= 20;
  console.log("攻撃しました。 HPが" + this.hp + "に減りました。");
};

var yuusha1 = new Yuusha("勇者1");
var yuusha2 = new Yuusha("勇者2");

//攻撃してもらう
yuusha1.attack();
yuusha1.attack();

yuusha2.jikoshokai();

新しいattackというメソッドを作りました。攻撃して、HPが20減るというメソッドです。普通なら敵のHPが減りますが、敵を作るのが面倒なので、疲れて自分のHPが減るということにしましょう。

ここで、this.hp -= 20;という一文があります。見ての通り、this.hpを20減らすという意味です。しかし、yuusha1はhpというプロパティを持たないので代わりにYuusha.prototype.hpを見ていたのでした。そこでthis.hp -= 20;とするとどうなるのでしょう。Yuusha.prototype.hpが減るのでしょうか。

実は、これはthis.hp = this.hp - 20;と同じ意味なので、 this.hpthis.hp-20が代入されるということになります。ここで、右辺の値を計算するにはthis.hpの値を知る必要があるのでYuusha.prototype.hp(ここでは100)をみてきます。したがって、右辺は80ということになります。それをthis.hpに今度は代入するわけですが、ここでthisやyuusha1です。代入においてはprototypeに頼る必要はありません。よってyuusha1.hpに80が入り、Yuusha.prototype.hpは100のままです。その証拠に、attackのあとでも勇者2のhpは100のままですね。

ただし、このように変動して各インスタンスに固有な値は、初期値をprototypeに入れておくのではなくコンストラクタで初期化しておくのがよいでしょう。すなわち、次のようにします。


function Yuusha(n){
  this.name = n;
  this.hp = 100;
}

prototypeには、主にメソッドなど、どのインスタンスでも変わらないようなものを入れておきましょう。

今回はここまでですが、次回もprototypeが活躍します。