JavaScript: prototypeをようやく理解した

基本

例えば、こうすると

var F = function(){};
F.prototype = { a: "F.prototype.a" };
var f = new F();
f.a
// -> "F.prototype.a"
f.b
// -> undefined
F.prototype.b = "F.prototype.b";
f.b
// -> "F.prototype.b"

こうなる。

prototypeを変更すると、それをもつオブジェクトに(既にnewされたものも含め)すべて反映される。

親プロパティの継承を実現するには

さらにprototypeに「prototypeをもつオブジェクト」をセットすると

var F2 = function(){};
F2.prototype = new F();
/*
(間違い)
コンストラクタを呼び出す必要がなければ
F2.prototype = F.prototype;
でもよい
*/
var f2 = new F2();
[f2.a, f2.b]
// -> ["F.prototype.a", "F.prototype.b"]
f2.c
// -> undefined
F.prototype.c = "F.prototype.c";
f2.c
// -> "F.prototype.c"

このようにF2.prototypeF.prototypeの変更が反映され、その結果f2からも参照できるようになる。

プロトタイプチェイン。

というわけで、親プロパティの継承を実現するには「prototypeをもつオブジェクト」(あるいはprototypeそのもの)をprototypeにセットすれば良い。

親コンストラクタを呼ぶには

var F3 = function(){
    F2.apply(this, arguments);
    // F3のコンストラクト処理など
};
var F4 = function(){
    F3.apply(this, arguments);
    // -> F3のコンストラクタを呼び、そこからF2のコンストラクタも呼ばれる
};

こうやって、親コンストラクタを子のコンテキストで呼ぶ。

Function#applyは関数をコンテキストを指定して実行する。

引数はすべてargumentsがもっているので、(子コンストラクタが必要としないのであれば)仮引数は記述しなくても良い(けど、あとから見てもわかるように記述しておいたほうが良いと思う)。

テスト

var F = function(){};
F.prototype.a = "F.prototype.a";
var F2 = function(){};
F2.prototype = new F();
F2.prototype.b = "F2.prototype.b";
var F3 = function(){};
F3.prototype = new F2();
F3.prototype.c = "F3.prototype.c";
var f3 = new F3();
[f3.a, f3.b, f3.c, f3.d]
// -> ["F.prototype.a", "F2.prototype.b", "F3.prototype.c", undefined]
F.prototype.d = "F.prototype.d";
[f3.a, f3.b, f3.c, f3.d]
// -> ["F.prototype.a", "F2.prototype.b", "F3.prototype.c", "F.prototype.d"]
// (間違い)
var F = function(){};
F.prototype.a = "F.prototype.a";
var F2 = function(){};
F2.prototype = F.prototype;
F2.prototype.b = "F2.prototype.b";
var F3 = function(){};
F3.prototype = F2.prototype;
F3.prototype.c = "F3.prototype.c";
var f3 = new F3();
[f3.a, f3.b, f3.c, f3.d]
// -> ["F.prototype.a", "F2.prototype.b", "F3.prototype.c", undefined]
F.prototype.d = "F.prototype.d";
[f3.a, f3.b, f3.c, f3.d]
// -> ["F.prototype.a", "F2.prototype.b", "F3.prototype.c", "F.prototype.d"]

まとめ

prototypeを何となく「newしたときにコピーされるもの」と考えるのは大間違いなので悔い改めよ、というお話。

追記

揚げ足を取るようで嫌なのだが、弾さんの以下の例では継承は行われない。

MyBox.prototype = Box.prototype; // ここで継承しているのに
「勝手に添削 - JavaScript 入門」を勝手に添削 - IT戦記

もうちょっと調べる。


関数一発でプロトタイプチェーンに繋げて、オブジェクトをクローンする。 - IT戦記

コンストラクタを操作できないので少し話は違うけど、すごい抽象化。

追追記

// F3.prototype = new F2(); の場合
>>> f3.__proto__
Object c=F3.prototype.c b=F2.prototype.b a=F.prototype.a
>>> f3.__proto__.toSource()
"({c:"F3.prototype.c"})"
>>> dir(f3.__proto__)
a    "F.prototype.a"
b    "F2.prototype.b"
c    "F3.prototype.c"
d    "F.prototype.d"
// F3.prototype = F2.prototype; の場合
>>> f3.__proto__
Object a=F.prototype.a b=F2.prototype.b c=F3.prototype.c
>>> f3.__proto__.toSource()
"({a:"F.prototype.a", b:"F2.prototype.b", c:"F3.prototype.c", d:"F.prototype.d"})"
>>> dir(f3.__proto__)
a    "F.prototype.a"
b    "F2.prototype.b"
c    "F3.prototype.c"
d    "F.prototype.d"

f3.__proto__.toSource()あたりの結果が違う。

たまたま動くだけで、意図されたものではないってことかな。

Comment: 0

Comment Form
Name
URL
Comment

Trackback: 1

Trackback URL
http://mayokara.info/note/trackback/173
Attention
スパム対策のため、当エントリへのリンクがないトラックバックをブロックしています。
JavaScript: Sub.prototype = Super.prototype; が駄目な理由
mayokara note 2008/06/12 20:47
JavaScript: prototypeをようやく理解した - mayokara noteの続き。>http://www.tokumaru.org/JavaScript/auto_inheritance_1.htm:title=19.メソッドの自動継承(1)>上記を実行すると、PointクラスとColoredPointは同一のメソッド群を持つことになります。従って...