JavaScript初級者から中級者になろう
九章第一回 オブジェクト指向とは
九章では、DOMから少し離れてオブジェクト指向について解説します。
オブジェクト指向とは
オブジェクトそのものについては、一章で少し解説しました。では、オブジェクト指向とは何なのでしょう。
オブジェクト指向とは、大まかにいうと、プログラムをそれぞれの動作をする部品にわけ、部品の組み合わせでプログラムをつくるというものです。このそれぞれの部品がオブジェクトというわけです。
例えば、RPGを作りたい場合を考えてみましょう。RPGにはいろいろなものが登場します。それら1つ1つが部品なのです。例えば、「勇者」がいます。「敵」もいます。「アイテム」とか「武器」というのもありますね。RPGはこれらの組み合わせで動いているのです。
これらをオブジェクトとして作り、それを使ってプログラムを作ればよいのです。ちょっとやってみましょう。
var yuusha = new Object();
yuusha.name = "勇者"; //勇者の名前
yuusha.power = 10; //勇者の攻撃力
var teki = new Object();
teki.name = "ザコ敵"; //敵の名前
teki.hp = 100; //敵のHP
//攻撃する
console.log(yuusha.name + "は "+ teki.name + "に" + yuusha.power + "ダメージをあたえた!");
実行すると「勇者は ザコ敵に10ダメージをあたえた!」と出ます。
この例では「勇者」と「ザコ敵」の情報がそれぞれオブジェクトに入ってます。オブジェクトの作り方は、まずnew Object()として空のオブジェクトを作り、そこにプロパティをくっつけるというやり方です。
また、オブジェクトは、「情報」だけでなく「機能」をもつこともできます。すなわち、プロパティだけでなくメソッドも持てるということです。
var yuusha = new Object();
yuusha.name = "勇者"; //勇者の名前
//自己紹介してもらう
yuusha.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
yuusha.jikoshokai();
「私の名前は勇者です。」と出ます。
さて、今回は、勇者オブジェクトにjikoshokaiというメソッドをもたせました。勇者に自己紹介をしてもらうメソッドです。今回、thisという謎のものが出ています。これを解説します。コードと結果を比べると、 this.name が "勇者"、すなわちyuusha.nameを表しています。ということは、thisはyuushaです。
これは何かというと、あるオブジェクトのメソッドが呼び出されたときに、thisはそのオブジェクト自体を表します。jikoshokaiはyuushaのメソッドだから、thisはyuushaになったのです。だから、今回の場合thisがyuushaであるのはjikoshokaiの中だけです。
さて、これまで解説してきましたが、まだまだこれは本当のオブジェクト指向ではありません。例えば、「勇者」が5人いたとしたらどうでしょう。勇者も1人で1つの部品ですから、オブジェクトは5つになります。今回自己紹介もしてもらいましょう。
var yuusha1 = new Object();
yuusha1.name = "勇者1";
yuusha1.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
var yuusha2 = new Object();
yuusha2.name = "勇者2";
yuusha2.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
var yuusha3 = new Object();
yuusha3.name = "勇者3";
yuusha3.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
var yuusha4 = new Object();
yuusha4.name = "勇者4";
yuusha4.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
var yuusha5 = new Object();
yuusha5.name = "勇者5";
yuusha5.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
//みんなに自己紹介してもらう
yuusha1.jikoshokai();
yuusha2.jikoshokai();
yuusha3.jikoshokai();
yuusha4.jikoshokai();
yuusha5.jikoshokai();
ちゃんとそれぞれオブジェクトを作って、nameプロパティとjikoshokaiプロパティを作っています。
しかし、何だか格好悪い気がしませんか?ここで、あることに気づくかもしれません。jikoshokaiは5人とも全く同じメソッドであるということです。thisのおかげでjikoshokaiの中にyuusha1〜yuusha5の変数が出てきてませんから、全部共通になっています。そこで、こうしようと思うかもしれません。
//先に自己紹介を作っておく
var jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
var yuusha1 = new Object();
yuusha1.name = "勇者1";
yuusha1.jikoshokai = jikoshokai;
var yuusha2 = new Object();
yuusha2.name = "勇者2";
yuusha2.jikoshokai = jikoshokai;
var yuusha3 = new Object();
yuusha3.name = "勇者3";
yuusha3.jikoshokai = jikoshokai;
var yuusha4 = new Object();
yuusha4.name = "勇者4";
yuusha4.jikoshokai = jikoshokai;
var yuusha5 = new Object();
yuusha5.name = "勇者5";
yuusha5.jikoshokai = jikoshokai;
//みんなに自己紹介してもらう
yuusha1.jikoshokai();
yuusha2.jikoshokai();
yuusha3.jikoshokai();
yuusha4.jikoshokai();
yuusha5.jikoshokai();
これは、みんな同じ関数なんだから、関数は最初に1個作っておいて、勇者1〜5にはそれぞれ代入するだけで済ませようということです。「yuusha1.jikoshokai=jikoshokai;」というようにjikoshokaiが2つ出てきていてややこしいですが、前者は勇者のオブジェクトのプロパティ名、後者は変数名で別物です。
注:さっきjikoshokaiはyuushaのメソッドだから、thisはyuushaになった
と述べましたが、ここではjikoshokaiは全く同じメソッドなのにそれぞれでthisが異なっています。これは、jikoshokaiメソッド自体がthisの情報を持っているのではなく、「yuusha1.jikoshokai();」のようにメソッドが呼び出されたときに、どのオブジェクトのメソッドとして呼び出されたか(この場合yuusha1)の情報がメソッドに伝えられることによります。
さて、これでも確かによくなりました。しかし、もっとよい方法があります。
それではどうすればよいのでしょう。実はまだ、いままでのやり方では、yuusha1〜yuusha5という別々のオブジェクトを、同じプロパティ名を持つように気をつけて作っているだけです。
yuusha1〜yuusha5は同じ「勇者」なのですから、同じ種類のオブジェクトはまとめて統一的に扱いたいですね。それがオブジェクト指向のキモともいえる部分です。
具体的にどうするかというと、まず「勇者」というオブジェクトの型を作ってしまうのです。そして、その「型」からいくつも勇者を作ります。「型紙」とか「金型」とかの型を想像しましょう。注:この型というのは他の言語でいう「クラス」に相当するものです。JavaScriptにおいても、将来的(ES6)にはクラスが機能として提供されるそうです。
それで、その型とはなにかというと、実は関数です。関数がそのまま型になります。例えば、勇者の型を作るなら、
function Yuusha(){
}
という感じです。中に何を書けばいいかはあとで解説します。
当然、型を作ったら使います。ここで出てくるのがnew演算子です。「new Object()」とかのあのnewです。一章第一回にも出てますが、あまり詳しく説明していませんでした。
実はこれ、演算子の一種ということになっています。といっても特殊な形をもつ演算子です。new Object()の例を見て分かるように、
new 関数名(引数)
という形になります。今までnew Object()とかのように引数が出てきた形はありませんが、実は関数なので引数をつけることもできます。これも後で解説します。とにかくこの形で「型」を使ってオブジェクトを作るということが大事なことです。
ではやってみましょう。
//Yuushaという関数の型を作る
function Yuusha(){
}
var yuusha1 = new Yuusha();
var yuusha2 = new Yuusha();
var yuusha3 = new Yuusha();
勇者を3人作ってみました。new演算子の返り値は、その型を用いて作ったオブジェクトになります。これなら、誰が見てもこの3人の勇者は間違いなく同じ種類のオブジェクトです。
さて、さっきと同じく自己紹介でもしてもらうためにはどうすればよいでしょう。当然、さっきと同じようにプロパティやメソッドを持たせればよいということになります。
//Yuushaという関数の型を作る
function Yuusha(){
}
var yuusha1 = new Yuusha();
yuusha1.name = "勇者1";
yuusha1.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
var yuusha2 = new Yuusha();
yuusha2.name = "勇者2";
yuusha2.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
var yuusha3 = new Yuusha();
yuusha3.name = "勇者3";
yuusha3.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
//自己紹介してもらう
yuusha1.jikoshokai();
yuusha2.jikoshokai();
yuusha3.jikoshokai();
これでちゃんと自己紹介してくれました。
さて、ここであることに気づくはずです。さっきと何も変わっていません。ObjectがYuushaになって同じ種類のオブジェクトだと示せただけで、効率の悪さは変わっていませんね。上で紹介した最初に作っておく方法もありますが、もっとこの「型」を活かした方法があります。
それには、第一にコンストラクタを利用します。コンストラクタとは、要するに型となる関数のことです。今回の場合Yuushaですね。
実は、new演算子でオブジェクトを作ったとき、関数は呼び出されています。
function Yuusha(){
console.log("コンストラクタ");
}
のようにするとわかります。
さて、それではどうするかですが、ここでまたthisが活躍します。実は、コンストラクタが呼び出されたとき、thisは今から作られるオブジェクトを表すのです。つまり、このコンストラクタでthisをいじればよいのです。まず、自己紹介のメソッドをこのコンストラクタの中で設定してみましょう。
//Yuushaという関数の型を作る
function Yuusha(){
this.jikoshokai = function(){
console.log("私の名前は" + this.name + "です。");
};
}
var yuusha1 = new Yuusha();
yuusha1.name = "勇者1";
var yuusha2 = new Yuusha();
yuusha2.name = "勇者2";
var yuusha3 = new Yuusha();
yuusha3.name = "勇者3";
//自己紹介してもらう
yuusha1.jikoshokai();
yuusha2.jikoshokai();
yuusha3.jikoshokai();
コンストラクタは勇者を作るときに毎回呼ばれるので、すべての勇者がちゃんとjikoshokaiというメソッドを持っていることになります。
ちなみに、コンストラクタから作られた個々のオブジェクトをインスタンスといいます。今回の場合、勇者1〜3がそれぞれ「勇者」のインスタンスというわけですね。
さて、まだnameの設定がすっきりしていません。こればかりは個々のインスタンスで違うものだから仕方ないという気がしますが、コンストラクタの引数を使えばもう少しすっきりさせられます。コンストラクタは関数なので、引数を持てるのでした。すなわち、
//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();
これは、コンストラクタの1行目で、引数に渡した名前をそのままプロパティnameに代入しています。
これで、個々の勇者を作るのはnew演算子の1行だけになりました。
しかし、まだ完璧というわけではありません。そのあたりを次回解説します。