[JavaScript] プロトタイプ・チェーンで最も問題になる点
2012年2月1日
最もわかり難い点は this.constructor の参照先
this.constructor が自分を生成した関数(クラス)を参照しなくなる点です
var Animal = function(){};
Animal.prototype.constructorIsAnimal = function(){
return this.constructor === Animal;
};
var Dog = function(){};
Dog.prototype = new Animal();
var animal = new Animal();
var dog = new Dog();
console.log(animal.constructorIsAnimal()); //-> true
console.log(dog.constructorIsAnimal()); //-> true !!重要!!
なので、Python の self.__class__ や PHP の self:: のつもりで使ってると
プロトタイプ・チェーンした際にバグります
問題になる点が問題になるのは自クラスへの参照が無いから
そもそも this.constructor 以外に自分の生成元を参照する方法が
先の === Animal のような静的名前解決以外にないからです
(コンストラクタ内に限定すれば arguments.callee で代用できます)
※念のため補足すると、「自分の生成元」ってのは
生成された object を new した Function のことです
つまり、先にも例に出したような self.__class__ や self:: が無いんです
なので、 self.__class__ が無いのかなぁ思った人の思考として
「いやぁそんなことないっしょ? ほらほら! this.constructor があるじゃない!」
という、一種の罠的なハマりになってしまうことが
プロトタイプ・チェーンの最たる問題点だと思ってます
ちなみに、この点の解決ですが
1. どこでも参照できるようにクラス変数名を管理し、常に静的名前解決
2. 自前の継承関数を作って prototype.__myClass__ 等へ生成元をキープ
3. そもそも生成元を参照しなくても良い設計にする、全部prototype内に持つなど
・・・位のあまりスマートで無い方法しか知りませぬ
1 は、クラス名を変えた時面倒
また、動的スコープを使ってどんなクラスにも有効なメソッドを作った時にコピれなくなる
2 は、継承方法がオレオレになっちゃう、でも自分はコレで解決してる
3 は、基本的にprototype内のプロパティ名は継承時の衝突が怖いので
出来る限り減らしたくなるはずで、その点に反する
他の問題点は?
後は、動的スコープである 「this の参照先をちゃんと把握できているか」とか
「newとfunc()の違いを抑えているか」という
JSのより基本的な論点の問題(?)とごっちゃになってるんじゃないかと思います
まぁ、特に後者はわかりにくいですけど・・・
俺は結構な期間JavaScriptを使って仕事してますが
それでもまだ this の参照先を間違えることがあります
特に「俺極めた、大丈夫」とか思ってる状態が一番危険です
本当はそれらが原因のバグが
プロトタイプ・チェーンのせいになってるだけじゃないかと思いました
一方、ロシアはプロトタイプ・チェーンを使わなかった
以下のような関数で、単に個別にprototypeのプロパティをコピーする方法です
var mixin = function(SubClass, superObj){
var k;
for (k in superObj) { SubClass.prototype[k] = superObj[k] };
};
mixin(YourSubClass, new YourSuperClass());
Arrayなどのネイティブクラスを継承する場合は
プロトタイプ・チェーンをした方が断然良いです(この理由はいずれ記事にします)
しかし、ユーザ作成クラスを継承する場合、プロトタイプ・チェーンによる利益は
「独自継承関数を定義しなくてよい」「instanceof で判定できるようになる」
以外に無い気がするんすよね