uhyohyo.net

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

九章第二回 prototypeの活用

今回は、prototypeというものを解説します。これは、前回の

//Yuushaという関数の型を作る
function Yuusha(n){
  this.name = n;
  this.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();
          

がまだ完璧ではないということとも関連しています。

まずどこが完璧でないかというと、関数オブジェクトが無駄だということです。基本的に、function (){ 〜 }とかくと、新しい関数オブジェクトが作られます。つまり、コンストラクタYuusha内で

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

と書いてあると、それぞれ新しい関数オブジェクトが作られることになり、結果勇者のインスタンス1つにつき関数オブジェクトも1つということになります。どのインスタンスでも同じ関数なのだから、関数オブジェクトも1つのほうがよいので、これはよくないということになります。

さて、これを解決するのがprototypeです。prototypeとは何かというと、コンストラクタのプロパティで、prototypeもオブジェクトです。コンストラクタとはつまり関数なので、関数ができたとき自動的にprototypeというプロパティもできています。

このprototypeはどんな役割をするかというと、簡単にいうと、その型のオブジェクトが共通に持つプロパティやメソッドは、コンストラクタのprototypeのプロパティやメソッドとして書いておけばいいのです。とりあえずやってみましょう。

//Yuushaという関数の型を作る
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();
          

このようにコンストラクタ(Yuusha)のprototypeにメソッドを付けておくことで、インスタンス(yuusha1〜3)から使うことができました。

これはなぜかというと、あるオブジェクトのプロパティやメソッドを使おうとしてもないとき、次にコンストラクタのprototypeを探すのです。それでもなければ、本当にないということでundefinedになります。

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

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

//Yuushaという関数の型を作る
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ということですね。

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

//Yuushaという関数の型を作る
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");

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

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

ここで、this.hp -= 20;という一文があります。そのとおり、this.hpを20減らすのです。しかし、ここでthis.hpは無いから、かわりにYuusha.prototype.hpを見ていたのです。そこでこれをするとどうなるのでしょう。Yuusha.prototype.hpが減るのでしょうか。 実は、これは this.hp = this.hp - 20; と同じことなので、 this.hp に this.hp-20が代入されるということになります。ここで、右辺の値を計算するにはthis.hpの値を知る必要があるのでYuusha.prototype.hp(ここでは100)をみてきます。したがって、右辺は80ということになります。それをthis.hpに今度は代入するわけですが、代入なのでprototypeに頼ることはなくそのままthis.hpに入り、80になります。だから、例えば2回目は、prototypeに頼ることなく、直接this.hpに80が入っているのです。

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

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

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

次回はこのprototypeがさらに活躍します。