prototype
はふわっと知っているけど、実はよくわかっていないので、調べて見た。
JavaScript は、クラスベースのオブジェクト指向と違って、あくまでプロトタイプベース。class
キーワードがあるが、単なるシンタックスシュガーである。
prototype
継承に関して言えば、JavaScript はオブジェクトしかない。各オブジェクトはプライベートプロパティ([[Prototype]]と呼ばれる) を持っていて、これは、他のオブジェクト(これは、prototypeと呼ばれる) を持っている。プロトタイプオブジェクトは、自身にプロトタイプオブジェクトを持っている。そのチェーンが null (つまりプロトタイプオブジェクトがnull) に到達すると、そのチェーンが終了する。これをプロトタイプチェーンと呼ぶ。イメージとしてはこんな感じか?
Object
JavaScript の全てのオブジェクトは、Object
のインスタンスになっている。Object
がプロトタイプチェーンのトップになる。JavaScript の オブジェクトは、ダイナミックなバッグみたいなもので、(own properties と呼ばれる) JavaScript のオブジェクトは、プロトタイプオブジェクトへのリンクをもつ。ある、オブジェクトのプロパティにアクセスすると、そのオブジェクトが持っているプロパティだけでなく、そのオブジェクトのプロパティになければ、プロトタイプのプロパティも参照される。そこになければ、その先のプロトタイプのプロパティ、、、といったようにそのチェーンを辿って参照する。それがnull になるまで。
prototype の参照
prototype は、obj.[[Prototype]]
に格納されているが、それはプライベートになっている。Object.getPrototypeOf()
や、Object.setPrototypeOf()
経由でアクセスされる。これは、__proto__
と同等である。
他にも、func.prototype
というものもある。これは、この関数のコンストラクタを使った時に、全てのインスタンスオブジェクトに対して、[[Prototype]]
をアサインするものになる。
prototype のサンプルコード
上記の理屈を理解すべく、サンプルコードを書いて見た。
var a = {
a: 1,
b: 2
}
var obj = Object.setPrototypeOf(a, {
b: 3,
c: 4
});
obj.__proto__ = {
c: 5,
d: 6
}
console.log(obj.a);
console.log(obj.b);
console.log(obj.c);
console.log(obj.d);
console.log(obj.e);
実行結果
1
2
5
6
undefined
Prototype のチェーンにないもの e
のみが undefined になり、それ以外は、チェーンを辿って表示されている。興味深いものは、b
の結果で、2番目のプロトタイプオブジェクトで、b=3
にしているが、先に、最初のオブジェクトですでに、b
が存在するので、その先のチェーンまで調査されない。
メソッドと、prototype
次に、メソッドタイプのものを作成してみる。
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v) {
this.vertices.push(v);
}
}
var g = new Graph();
console.log(g.hasOwnProperty('vertices'));
console.log(g.hasOwnProperty('edges'));
console.log(g.hasOwnProperty('addVertex'));
console.log(g.__proto__.hasOwnProperty('addVertex'));
console.log(Object.getPrototypeOf(g));
前回のポストでもある通り、Graph.prototype
を用いると、インスタンス作成時に、毎回メソッド分のメモリが必要にならない。これは、addVertex
のメソッドが、プロトタイプからのリンクになっているからで、新しいインスタンスを作っても、単にそこにリンクが貼られるだけだからだ。だから、上記のプログラムを作って、どこにプロパティがあるかを調査すると、メソッドのみが、プロトタイプに存在することがわかる。
true
true
false
true
{ addVertex: [Function: addVertex] }
試しに、同じ方式で、プロパティもいけるか試してみる。
function SomeObj(name, age) {
this.name = name;
this.age = age;
}
SomeObj.prototype = {
name: "Proto desu",
salary: 100,
print: function() {
console.log(this.name + " " + this.age);
}
}
var some = new SomeObj("Tsuyoshi", 46);
some.print();
console.log(some.salary);
console.log(Object.getPrototypeOf(some));
実行結果
Tsuyoshi 46
100
{ name: 'Proto desu', salary: 100, print: [Function: print] }
しっかり、想定通りの結果になっている。プロパティも同じ方法が使える。
まとめ
これでかなりプロトタイプに関して学べた気がします。挙動がわかってスッキリ!次は、JavaScript の関数に関して書いて見ます。
リソース
今回の記事はほぼこのページから学びました。