プロトタイプベースとクラスベース
C++、Javaのようにクラス-インスタンスという概念を使用するオブジェクト指向言語のことを、クラスベースオブジェクト指向言語(Class Based Object Oriented Language)と言います。
それとは異なり、JavaScriptでは、クラス-インスタンスという考え方をせず、オブジェクトは別なオブジェクトを元(プロトタイプ)にして独自の特徴を付加することで存在する、という考え方をします。
このようなオブジェクト指向言語のことを、プロトタイプベースオブジェクト指向言語(Prototype Based Object Oriented Language)と言います。
JavaScriptでは、オブジェクト自身が独自の特徴を付加するので、オブジェクトにプロパティを追加したり削除したりすることができます。
コンストラクタという機構
JavaScriptでオブジェクトを生成するには、コンストラクタとnewという演算子を使用します。
実はコンストラクタとは関数オブジェクトの事です、全ての関数はオブジェクトを生成するコンストラクタになる可能性がありますが、まずは一番シンプルな例を見てください。
単純なコンストラクタ
function SimpleConstructor() {}
var obj = new SimpleConstructor();
普通にfunction文で定義した関数にnewをつけて呼び出しているだけです。こうすることで、新しいオブジェクトが生成され、戻り値として返されます。
return文を記述しなくても生成されるオブジェクトが返される事に注意してください。
通常、コンストラクタとして呼ばれる関数の中では、生成しようとするオブジェクトの初期化を行います。
function PropSetConstructor() {
this.prop1 = 10;
this.prop2 = 20;
}
var obj1 = new PropSetConstructor();
alert(obj1.prop1); // 10 と表示される。
var obj2 = new PropSetConstructor();
alert(obj2.prop1); // 10 と表示される。
コンストラクタとして関数オブジェクトを実行した場合、thisには今新たに生成されようとしているオブジェクトが入ります。
従って、thisにプロパティを設定してやれば、newして作成されるオブジェクトの初期定義を行う事ができます。
こうする事で、同じコンストラクタを用いて複数のオブジェクトを生成すると、同じプロパティを持つオブジェクトを簡単に作成する事ができます。
もちろんdelete演算子を使用すればプロパティを削除できるので、同じコンストラクタから作成されても常に必ず同じプロパティを持つとは限らない事に注意してください。
暗黙の参照
プロトタイプベースのオブジェクト指向言語では、オブジェクトトは別のオブジェクトをプロトタイプとしてできていると考えます。JavaScriptではこれを暗黙の参照という形で実現している。オブジェクトAをプロトタイプとしているオブジェクトBは、オブジェクトAに対し暗黙の参照を持っているという状態になります。
Javaで言うなれば、非staticなinner classのインスタンスがenclosing classのインスタンスに対し、暗黙の参照を持っている状態を考えてもらえば良いです。つまりしたのような状態になります。
暗黙の参照:
var objectA = ...
// objectB は objectA に対し暗黙の参照を持っている
// (objectAはobjectBのプロトタイプである)。
var objectB = ... objectA.hoge = 10; // objectA の hoge プロパティをここで設定する。
alert(objectB.hoge); // 10 と評価される。
すなわち、あるオブジェクトがプロパティを評価された時、自分自身がそのプロパティを持っていなければ、暗黙の参照をたどって、その先のオブジェクトのプロパティを評価します。
これがJavaScriptにおけるプロトタイプの仕組みです。
prototypeオブジェクト
全ての関数オブジェクトはprototypeというプロパティを保持している。関数オブジェクトを定義した直後では、このprototypeには何もプロパティを持たないシンプルなオブジェクトを参照しているが、別のオブジェクトを代入したり、新たなプロプロパティを設定したりする事が可能です。
その関数オブジェクトをコンストラクタとして生成されたオブジェクトは、コンストラクタのprototypeプロパティに代入されているオブジェクトに対し、暗黙の参照を持つのです。つまりprototypeが指すオブジェクトトがプロトタイプとなるというわけです。
具体的にコードは以下:
prototypeオブジェクト
function PrototypeTestConstructor() {}
// prototype オブジェクトに prop1 というプロパティを設定。
PrototypeTestConstructor.prototype.prop1 = 30;
var obj = new PrototypeTestConstructor();
alert(obj.prop1); // 30 と表示される。
つまりPrototypeTestConstructorによって生成されたオブジェクトは、PrototypeTestConstructor.prototypeが指すオブジェクトを暗黙的に参照するようになります。
あるコンストラクタをnewしてできるオブジェクトが全てそのprototypeへの暗黙の参照を持つので、そのプロパティはクラス変数の様にnewされたオブジェクトで共有されることとなる。次の例を見てください。
代入はprototypeをたどらない。
function Constructor() {}
Constructor.prototype.prop1 = 30;
var objA = new Constructor();
var objB = new Constructor(); // [α]
alert(objA.prop1) // 30 と表示される。
alert(objB.prop1) // 30 と表示される。
objA.prop1 = 100; // [β]
alert(objA.prop1) // 100 と表示される。
alert(objB.prop1) // 30と表示される
というのも、読み取り評価の時は、暗黙の参照をたどるのですが、代入やdelete演算子は、たどらないのである。従って、objA.prop1 = 100;を行った時点で、objAそのものにprop1というプロパティが新たに作られ、そこに100が代入されるのです。よってConstructor.prototype.prop1の値は変わらないままとなります。
この場合、objA.prop1を呼び出すと、objA自身にはprop1があるため、その値である100と評価され、objB.prop1を呼び出すと、暗黙の参照をたどり30と評価されます。
この仕組みがプロトタイプベース言語であるJavaScriptのオブジェクトの根幹となります。
プロトタイプチェーン
コンストラクタのprototypeには、オブジェクトも代入できるので、prototypeに代入したオブジェクトが別のオブジェクトをプロトタイプにしている事もあります。そもそも全てのオブジェクトはObject.prototypeを暗黙的に参照しています。
こうしたプロトタイプの連鎖のことをプロトタイプチェーンと呼びます。
プロトタイプチェーン:
var objA = new Object();
objA.prop1 = 10;
function Func1() {}
Func1.prototype = objA;
var objB = new Func1();
function Func2() {}
Func2.prototype = objB;
var objC = new Func2();
alert(objC.prop1); // 10 と評価される。
以上までがJavaScriptのプロトタイプのメカニズムです。JavaやC++といったクラスベースの言語には根本的に無い考え方なので若干戸惑うかも知れないが、全てのオブジェクトがが実体を持った別のオブジェクトトに連鎖しているということが分かれば整理できます。
ではあるオブジェクトのプロパティを評価した際、仮に何らかの値が返ってきたとしても、そのオブジェクト自身がプロパティを持っているのか、そのオブジェクトのプロトタイプが持っているのか、判断することができません。
そこで、JavaScriptでは全てのオブジェクトに(すなわちObject.prototypeに)、hasOwnPropertyというメソッドが定義されている。これは引数で与えた文字列に一致するプロパティを、そのオブジェクト自身が持っているかどうかを判断してくれるメソッドです。
具体例を見てください。
function Constructor() {}
Constructor.prototype.prop1 = 30;
var objA = new Constructor();
// objA 自体は持っていないので false。
alert(objA.hasOwnProperty("prop1"));
objA.prop1 = 100;
// objA 自体が持っているので true。
alert(objA.hasOwnProperty("prop1"))
このhasOwnPropertyメソッドを使うことで、そのオブジェクトト自身にプロパティがあるかどうかを判断することができます。
継承
継承に関して言えば、 JavaScript には 1 つだけオブジェクトに関する概念があります。あるオブジェクトはプロトタイプと呼ばれる、他のオブジェクト(または null )への内部的な繋がりを持ちます。このオブジェクトは、あるオブジェクトがそのプロトタイプとして null を持つまで、なおプロトタイプを持ちます。このような、オブジェクトが他のオブジェクトのプロトタイプとなることの連鎖を、プロトタイプチェーンと呼びます。
プロパティの継承
JavaScript のオブジェクトはプロパティ(自身のプロパティを指す)の動的な「かばん」で、プロトタイプオブジェクト(または null )への繋がりを持っています。
あるオブジェクトにプロパティをセットすると、自身のプロパティが作られます。この取得と設定の動作の規則の唯一の例外は、 getter または setter とのプロパティの継承が起こるときです。
「メソッド」の継承
JavaScript それ自体に「メソッド」はありません。 JavaScript には関数があり、これらの関数はプロパティの値として使用することができます。 関数の継承(メソッドのオーバーライドの一種)は、上で見せたような property shadowing を含めたどのような値とも、同じように働きます。
関数がオブジェクトのプロプロパティ(自身の、または継承された)であることのたった1つの違いは、関数が実行されるときの this の値です。