今回は、「オブジェクト指向」についてご紹介させて頂きます。
前回ご説明させて頂いた、「オブジェクト」についてきちんと理解していることが前提です。
もし、「オブジェクト」についての説明を読んでいない方は、事前に以下の記事をお読みいただくとことをお勧めします。
「オブジェクト指向」自体はそれほど難しくはないのですが、基本をしっかり理解していないと、コードが長くなった時に分からなくなってしまいます。
ここではとてもシンプルなコードを書いていますので、ぜひ実際にご自身でコードを書いて学習してください。
コンストラクタ関数
ここではイメージとして、個人カードを作成するオブジェクト指向プログラミング(OOP)について見ていきます。
名前と年齢、住所と趣味についてプロパティを作成して、アラート関数の入ったメソッドを記述していきます。
let Profile = function(name,age,address,interests){ this.name = name; this.age = age; this.address = address; this.interests = interests; this.greeting = function(){ alert('私は'+ this.name + 'です。' + '年齢は' + this.age + 'です。' + '住所は' + this.address + 'です。' + '趣味は' + this.interests[0] + 'と' + this.interests[1] + 'です。' ); }; }
まずはオブジェクトを定義します。
このようにオブジェクトを定義する関数のことを「コンストラクタ関数」と言います。
定義の仕方は前回とほぼ同じですが、すべてのkeyに「this」を記述しています。
また、変数が「Profile」と先頭の単語が大文字になっていることに注意してください。
let person1 = new Profile('太郎', 33 , 'Tokyo', ['サッカー', '水泳']); person1; Profile {name: '太郎', age: 33, address: 'Tokyo', interests: Array(2), greeting: ƒ} address: "Tokyo" age: 33 greeting: ƒ () interests: (2) ['サッカー', '水泳'] name: "太郎" [[Prototype]]: Object
「オブジェクトインスタンス」という定義されたデータや機能を持ったオブジェクトを作成するため(簡単に言うと個人カードを作成するため)に、「let person1 ~」で必要な引数を指定します。
インスタンスを作成するときは、「new演算子」を記述することを忘れないでください。
「person1」には、指定した引数が格納されているのが確認出来ると思います。
let person2 = new Profile('花子', 30, 'Yokohama', ['テニス', '水泳']); person2; Profile {name: '花子', age: 30, address: 'Yokohama', interests: Array(2), greeting: ƒ} address: "Yokohama" age: 30 greeting: ƒ () interests: (2) ['テニス', '水泳'] name: "花子" [[Prototype]]: Object
さらに別の人を追加したい場合も同様に記述すれば追加することが出来ます。
このように、事前に登録する人数が不明な時に、最初にオブジェクトを定義しておくと大変便利なことが分かります。
プロトタイプ (prototype)
プロトタイプとは
まず最初に「prototype」とは何か考えていきましょう。
オブジェクトは定義したときから、様々な「メソッド」を持っています。
空のオブジェクトであってもメソッドを持っています。
(元々持っているものなので、ビルトインメソッドと呼ばれることもあります)
(メソッドのイメージが湧かない場合はプロパティと考えてください)
この元々持っているメソッドのことを、「プロトタイプ(prototype)」と呼びます。
(プロトタイプメソッド、プロトタイププロパティと呼ばれることもありますが、ここではプロパティと呼ぶことにします)
言葉では分かりにくいと思いますので、実際に見てみましょう。
let Person = {}; console.log(Person); {} [[Prototype]]: Object constructor: ƒ Object() hasOwnProperty: ƒ hasOwnProperty() isPrototypeOf: ƒ isPrototypeOf() propertyIsEnumerable: ƒ propertyIsEnumerable() toLocaleString: ƒ toLocaleString() toString: ƒ toString() valueOf: ƒ valueOf() __defineGetter__: ƒ __defineGetter__() __defineSetter__: ƒ __defineSetter__() __lookupGetter__: ƒ __lookupGetter__() __lookupSetter__: ƒ __lookupSetter__() __proto__: (...) get __proto__: ƒ __proto__() set __proto__: ƒ __proto__()
「Person」という空のオブジェクトを定義してみました。
「Person」をコンソールに出力させると、このように様々なメソッドが格納されているのが確認出来ると思います。
これらが「プロトタイプ」と呼ばれるメソッドになります。
使い方
let Profile = function(name,age,address,interests){ this.name = name; this.age = age; this.address = address; this.interests = interests; this.greeting = function(){ alert('私は'+ this.name + 'です。' + '年齢は' + this.age + 'です。' + '住所は' + this.address + 'です。' + '趣味は' + this.interests[0] + 'と' + this.interests[1] + 'です。' ); }; }
これは先程、「コンストラクタ関数」を説明する時に使用したコードです。
このコードに対してインスタンスを作成すると、すべてのプロパティが引き継がれていました。
インスタンスが少ししかない場合は問題ありませんが、100や200、1000などどインスタンスが増えた場合、複数のメソッドが記述されていると動作的に良くありません。
(メモリを無駄に消費してしまいます)
そこで、「プロトタイプ」を使ってこのコードを以下のように書き換えてみます。
let Profile = function(name,age,address,interests){ this.name = name; this.age = age; this.address = address; this.interests = interests; } Profile.prototype.greeting= function(){ alert('私は'+ this.name + 'です。' + '年齢は' + this.age + 'です。' + '住所は' + this.address + 'です。' + '趣味は' + this.interests[0] + 'と' + this.interests[1] + 'です。' ); }
「Profile.prototype.greeting」でメソッドをプロトタイプにしています。
こうすることにより、コンストラクタ関数ではメソッドが新しいインスタンスに渡されていたのですが、渡されなくなりました。
let person1 = new Profile('太郎', 33 , 'Tokyo', ['サッカー', '水泳']); person1; Profile {name: '太郎', age: 33, address: 'Tokyo', interests: Array(2)} address: "Tokyo" age: 33 interests: (2) ['サッカー', '水泳'] name: "太郎" [[Prototype]]: Object
「greeting: ƒ ()」の部分が渡されていないのが確認出来ます。
簡略化
先程ではメソッドが一つしでしたが、複数のメソッドがある場合を考えてみましょう。
let Profile = function(name,age,address,interests){ this.name = name; this.age = age; this.address = address; this.interests = interests; } Profile.prototype.greeting= function(){ alert('私は'+ this.name + 'です。' + '年齢は' + this.age + 'です。' + '住所は' + this.address + 'です。' + '趣味は' + this.interests[0] + 'と' + this.interests[1] + 'です。' ); } Profile.prototype.watch = function(){ alert(this.interests[0] + 'はテレビでよく見ます。'); }
このようにメソッドが増えていくと、とても見づらくなりますし、後で管理するのも手間になる場合があります。
そこで、より分かりやすく、管理しやすくするために、プロトタイプに定義するメソッドをまとめて記述する方法が以下になります。
Profile.prototype = { greeting : function(){ alert('私は'+ this.name + 'です。' + '年齢は' + this.age + 'です。' + '住所は' + this.address + 'です。' + '趣味は' + this.interests[0] + 'と' + this.interests[1] + 'です。' ); }, watch : function(){ alert(this.interests[0] + 'はテレビでよく見ます。'); } }
複数のメソッドを、オブジェクトとして記述しています。
このようにプロトタイプでまとめておけば、どこを直せばよいのか分かりやすくなると思います。
プロトタイプの継承
ここではプロトタイプによる「継承」について見ていきましょう。
まずは「継承」の基本的な概念となる「プロトタイプチェーン」について理解していきます。
プロトタイプチェーン
let Greeting = function(){}; let Introduce = function(){};
ここに「Greeting」と「Introduce」という2つのオブジェクトがあります。
この2つのプロトタイプを連結させてみます。
Introduce.prototype = new Greeting();
もの凄く簡単で、「prototype」を使って式を書くだけです。
これで、それぞれのプロトタイプを参照することが出来るようになりました。
では、オブジェクトが3つあった場合はどうでしょうか。
let Greeting = function(){}; let Introduce = function(){}; let Interests = function(){};
こんどは「Interests」とというオブジェクトが追加になりました。
それでは、この「Interests」もつなげてみます。
Interests.prototype = new Introduce();
式は先程と同様に、「Interests」を「Introduce」に「prototype」を使って書くだけです。
これで何が起こるでしょうか?
すでに、「Introduce」と「Greeting」がつながっているために、「Interests」は「Greeting」を参照出来るようになっています。
ここで見たように、プロトタイプをチェーンのようにつなぐことを「プロトタイプチェーン」と言います。
継承
プロトタイプチェーンが理解出来たら、「継承」を理解するのは簡単です。
以下コードで見てみましょう。
let Greeting = function(){}; let Introduce = function(){}; Greeting.prototype.hello = function(){ return 'よろしくお願いします'; } Introduce.prototype = new Greeting(); let person1 = new Greeting(); let person2 = new Introduce(); person1.hello(); 'よろしくお願いします' person2.hello(); 'よろしくお願いします'
「Greeting」オブジェクトに、プロトタイプを使って「hello関数」を定義しています。
そして「Greeting」と「Introduce」を連結しています。
変数「person1」と「person2」の引数にそれぞれ、「Greeting」と「Introduce」を指定して、「hello関数」を指定すると、「よろしくお願いします」と返されます。
let Interests = function(){}; Interests.prototype = new Introduce(); let person3 = new Interests(); person3.hello(); 'よろしくお願いします'
さらに、「Interests」オブジェクトを追加して、「Introduce」と連結して、「hello関数」を指定すると「よろしくお願いします」と返されます。
ここで見てきたように、「Greeting」と「Introduce」と「Interests」がプロトタイプチェーンによりつながることを、「継承」と言います。
あとがき
「コンストラクタ関数」、「プロトタイプ」など沢山のキーワードが登場しましたが、用語に惑わされずに基本をしっかり身につけるようにしましょう。
「オブジェクト指向」に関しては、最初の内はあまり使う機会がないかもしれません。
しかし、いずれ複雑なコードに出くわすことがあると思います。
その時は、基本的に定義されたものがあり、それを参照または継承しているということを忘れないでいてください。
長いコードも一つ一つ紐解いていけば何とか理解出来ると思います。
今回も最後までお読み頂きありがとうございました。