最近の悩み。
ECMAScript言語仕様第3版を読んで、
オライリーの
これも読んで、
Mozillaの
ここも読んだけれど、
やっぱりまだわからない。
それは、Javascriptオブジェクトの継承。
例えば、prototype.jsを見てみると、
という感じで、
プロパティを単純にコピーしている。
この場合、
Object.extend(A.prototype, B.prototype)とすると、
Java的にはclass A extends Bと言いたくなる。
先を急がず、一方、YUI(Yahoo UI Library)では、
となっている。
prototype.jsと異なっているのは、
superclassのコンストラクタを呼ばずに、
テンポラリなコンストラクタを用意してオブジェクトを生成し、
それをsubclassのprototype属性に入れているところ。
結果としては、subclassのprototypeには、
superclassのプロトタイプ「のみ」が反映されたオブジェクトが、
baseclassのprototypeに代入される。
ところで、
というコンストラクタを定義したとしよう。
このオブジェクトを継承しようとした場合、
prototype.jsのextendもYUIのextendも、
両方ともそれだけでは継承として機能しない。
prototype.jsの場合は、
そもそもprototypeにフィールドやメソッドを定義しておく必要がある。
YUIの場合は、一見するとよさそうだけれども、
実際は空のコンストラクタにより生成されたオブジェクトが利用されるので、
結果としてはfooもbarも継承されない。
では、prototypeにちゃんとフィールドやメソッドを
定義しておけばいいじゃないかという話なのだが、
そうすると、上記Hogeにおけるプライベートフィールドaを
読み取ることが出来なくなってしまう。
(つまり、barからはaが見えなくなってしまう)
そこで、MizillaのJavascript解説でも、
オライリーの例の本でも同様に行なわれいている継承方法が、
である。
確かに、この方法であれば、
Bのコンストラクタが実行されるため、
Hogeで言うところのaを読み取るbarも正常に継承される。
ところが厄介なことに、
(クラス概念を持つオブジェクト指向で言うところの、)
親クラスのコンストラクタを子クラス内で実行した場合、
コンストラクタが2回実行されてしまう。
例えば、
この場合、B.prototype = new A;で一度Aのコンストラクタが、
引数を伴わずに実行され、その結果がB.prototypeに格納される。
この時点で、Bのインスタンスを生成すれば、
自動的にAのもつフィールドやメソッドを継承している。
ところが、Aのコンストラクタは本来引数を伴うべきなので、
Javaのsuper()よろしく、
Bのコンストラクタ内からAのコンストラクタを
thisに新規オブジェクトをバインドさせた状態で呼び出す。
そうすると、Aのコンストラクタの2回目が実行され、
さらに悪いことに、
new B;の結果として生成されたオブジェクトは、
prototype内にAのフィールドとメソッドを持ちながら、
そのオブジェクト自体に、
Aのフィールドやメソッドがアタッチされてしまう。
よって、悩む、悩む。
みなさんはどうやっているのでしょうか?
個人的には、
prototypeには継承元オブジェクトのみを代入して、
コンストラクタでフィールドとメソッドを作ることが、
最も可読性が高く、直感的だと思っているのですが。
(「prototypeに入れたほうがメモリ抑えられるよ」という話は別として)
悩む、悩む。