サイ本読書記 9章 クラスとコンストラクタとプロトタイプ(前半

9章です。9章は難しい上にページ数が多いので、前半と後半に分けたいと思います。 ほんと難しいので、後半は今の時点ではスルーするかもしれません。

9章は、JavaScriptを書く上の必須知識というよりは、オブジェクト指向的に書くにはどうするのかという内容かなと感じました。 そういえばこの前書いてみたJS(ぽいもの)を先輩に「オブジェクト指向ぽくないな」と言われました。

コンストラクタ関数

新しく生成したオブジェクトの初期設定をする関数。初期設定とはプロパティに値を設定するなど。

// コンストラクタ関数
function Rect(w,h) {
 this.width = w;
 this.height = h;
}

// オブジェクトを生成する時にコンストラクタ関数で設定
var rec1 = new Rect(5,10);
var rec2 = new Rect(99,999);

これで以下のようなオブジェクトになる。

rec1 = {width:5, height:10};
rec2 = {width:99, height:999};

new演算子でオブジェクトを生成する時に、引数をコンストラクタ関数に渡している。

クラスとインスタンス

正確にはJSにクラスは無いけど、他に呼び方もないしクラスっぽいからクラスって言うよというのがサイ本の記述。
コンストラクタ関数がクラス、生成されたオブジェクトがインスタンス

クラスとインスタンスはしばしば設計図とそれを元に作られたモノに例えられるけど、そんな感じらしい。コンストラクタ関数が設計図で、生成したオブジェクトがモノ。
一度コンストラクタ関数を書いておけば、後はオブジェクトを生成する時に引数を渡すだけでよい。だから、似たオブジェクトをたくさん作る時とかに効率とか良い。

なお、コンストラクタ関数の命名には生成するオブジェクトの名前を付けるのがお勧め、との事。つまりオブジェクトに合わせて関数名を決めるという事。

プロトタイプと継承

function rect(r) {return r.width * r.height;}

というコードは、動くことは動くがオブジェクト指向的では無い、らしい。

var r = new Rect(50,100);
r.area = function(){return this.width + this.height;}

var a = r.area();

とすればオブジェクト指向的だけど、メソッドを実行するためにメソッドをオブジェクトに追加しているのがちょっと無駄。
更にこれを書き直すと、

function Rect(w,h) {
 this.width = w;
 this.height = h;
 this.area = function() {return this.width * this.height;}
}

var r = new Rect(50,100);
var a = r.area();

こうなる。処理はコンストラクタ関数にまとめてしまう、みたいな感じ?確かにこっちの方がすっきりして解りやすい気がする。
オブジェクト指向はプログラミングをラクにする為の物らしいので、コードを直感的にする事は大事なんじゃないかと思った。

でも、サイ本はこれでもベストでは無いと言う。
なぜなら、このコンストラクタ関数を使う全てのインスタンスがメソッドも含めてしまうから。

this.widthとthis.heightの二つのプロパティは、値がwとhとなっている事からも分るように、インスタンス毎に値が異なる。なのでこれは問題ない。
でもthis.areaは関数が記述されているだけなので、どのインスタンスでも同じ内容になる。これは非効率的。

この問題を解決するために、プロトタイプというものを使う。

プロトタイプ

JSの全てのオブジェクトは、プロトタイプオブジェクトを参照できる。
プロトタイプオブジェクトのプロパティは、プロトタイプオブジェクトを参照するオブジェクトのプロパティとして使える。つまり、プロトタイプオブジェクトのプロパティは全てのオブジェクトで使う事ができる。

function Rect(w,h) {
 this.width = w;
 this.height = h;
}

var rec1 = new Rect(5,10);
var rec2 = new Rect(99,999);

上記のコードでオブジェクトを生成すると、流れ的には

  1. new演算子で空のオブジェクトを生成
  2. new演算子が空のオブジェクトに対してプロトタイプを設定
  3. コンストラクタ関数の処理が行われる
  4. rec1とかrec2とかになる

となる。

2で設定されるプロトタイプとは、コンストラクタ関数のprototypeプロパティの値。
prototypeプロパティとは、全ての関数が定義時に自動で生成されるプロパティ。値も設定される。
prototypeプロパティの初期値は、constructorプロパティのみを持つオブジェクト。
constructorプロパティは、コンストラクタ関数自身を参照する。

インスタンスごとに変わらないものにはプロトタイプを使う
function Rect(w,h) {
 this.width = w;
 this.height = h;
}

Rect.prototype.area = function() { return this.width * this.height;}

var rec1 = new Rect(5,10);
var rec2 = new Rect(99,999);

コンストラクタ関数はクラスに当たり、widthやheightのプロパティを設定する。
コンストラクタ関数で設定されたオブジェクト(rec1やrec2)は、コンストラクタ関数に関連付けられたプロトタイプオブジェクト(Rect.prototype)から同じプロパティ(area)を継承する。
この継承は自動で行われる。
このような書き方をすると、メモリが節約できる。