jsのプロトタイプについてまとめてみた


なんか理解がぼんやりしていたのでまとめてみました

JavaScriptはプロトタイプベースっていうけど、それって何?


プロトタイプベースとは、全てのオブジェクトは他のオブジェクトのクローンを派生させたものという設計のオブジェクト指向。派生元をプロトタイプという
プロトタイプベースでは、自身が定義していない要素にアクセスした場合、プロトタイプをたどって要素を探しに行く。これをプロトタイプチェーンと言う

Javaみたいなクラスベースとの違いは?


クラスベースでは、全てのオブジェクトは静的なクラスから生成される。
その為ちょっとしたクラスの派生を作りたい場合にも、クラス新たに定義する必要があるし、また基本的には動的にクラスを定義できない
プロトタイプベースでは、オブジェクトに要素を自由に追加出来るので、柔軟なのが利点

JavaScriptではどうやって、オブジェクトを生成するの?


JavaScriptでは、オブジェクトの生成は基本的にはnew演算子で行う

  var a = new A();


new演算子がやることは、

  • 引数に渡した関数オブジェクト(コンストラクタ)が持つプロトタイプを引き継いだオブジェクトを生成する事
  • それをthisとして関数オブジェクトを実行すること


あと、newで生成されたオブジェクトをインスタンスと呼ぶ。

  • instanceof演算子で、どの関数オブジェクトから生成されたのか調べる事が可能

newしたり、生成されるオブジェクトがインスタンスだったり、クラスベースっぽいね


newやinstanceof演算子の為に、クラスベースっぽく見えるけど、JavaScriptはれっきとしたプロトタイプベースの言語。
クラスベースっぽく見えるのは、ネットスケープJavaScriptを公開する時にJavaサーブレットも同時に公開する為に、JavaScriptJavaっぽくしたかったというマーケティング的な理由があったからと思われる

プロトタイプっぽいところは?


JavaScriptでは、prototype要素にプロトタイプの情報を格納する。
prototype要素は関数オブジェクト生成時に自動的に、constructor要素にその関数オブジェクトを入れたものが生成される
ちなみに、newを実行した時には、そのconstructor要素が呼ばれるので、中身を差し替える事で、呼ばれるコンストラクタを動的に変更できる。キモい!

var A=function(name){
  this.name = name;
}
A.prototype.say = 'hello';

var a = new A('Bob');
a.name; // Bob
a.say; // hello


プロトタイプはインスタンスが個別に持つのではなく、new した関数オブジェクトのprototypeを参照するので、newした後の変更も反映される

A.prototype.say = 'hi!';
a.say; // hi!

プロトタイプチェーンを使って継承っぽいことができるらしいけど?


web上ではこんなやり方が多い

var Asub = function(){...}
Asub.prototype = new A();
Asub.prototype.constructor = Asub; // prototypeを上書きしたので、コンストラクタを再定義
Asub.prototype.add = function{ ... } // 追加したい要素を定義していく


継承元の関数オブジェクトをnewしたインスタンスをプロトタイプに入れる
Aの持つ要素を全てAsubのプロトタイプに入れるイメージ
コンストラクタで定義された要素もプロトタイプに入るから漏れがないように感じる


だけど、色々欠点がある

  • コンストラクタに引数がある場合に動作出来ない
  • コンストラクタを呼びだす毎に内容が変わる場合に対応できない
  • コンストラクタで定義された要素と、prototypeがごっちゃになる


例えば、こんなことしたい場合

// 面積
var A = function(width, height){
  this.width = width;
  this.height = height;
}
A.prototype.area = function(){ return this.width*this.height; }

// 体積
var Asub = function(width, height, depth){...}
Asub.prototype.volume = function{ return this.width*this.height*this.depth; }

// これだと、A側のコンストラクタを引数付きで実行できない!
Asub.prototype = new A();    


なので、サイ本とかだと以下の方法をおすすめしている

// applyで、継承元のコンストラクタをコンストラクタで実行する
var Asub = function(width, height, depth){
  A.apply(this, [width, height]);
}
// Object.createは、引数のプロトタイプを持つオブジェクトを生成するメソッド
Asub.prototype = Object.create(A.prototype);
Asub.prototype.constructor = ASub;


コンストラクタ側で、継承元のコンストラクタも呼ぶ
んで、newするのではなく、Object.createでprototypeを定義する
これで、継承元のコンストラクタと、prototypeをごっちゃにせずに引き継ぎすることが可能


そんな感じです