自分用class定義ライブラリmyclass.jsを作ってみる(1)
自分の中でデファクトとすべく書き方を考えてみました。
いろいろと発見もあったので、はてな界隈の方々にはいまさらかとは思いますが、まとめてみました。
まずは単純に
汎用ルーチンとかに頼らず書いてみる。
/* 親 */ var Car = function(){} Car.prototype = { voice : 'boo', tire : 4, init : function(p){ if(p){ this.tire = p.tire; } return this; }, showSpec : function(){ alert('voice is ' + this.voice) alert('tire is ' + this.tire) return this; } } /* 子 */ var DumpCar = function(){} DumpCar.prototype = new Car(); /* 継承 */ DumpCar.prototype.voice = 'GAAAA!'; DumpCar.prototype.hasBed = 'yes'; DumpCar.prototype.init = function(p){ Car.prototype.init.apply(this,arguments) return this; } /* 実行 */ ( (new DumpCar()).init({tire:6}).showSpec())
プロパティ定義方法の親と子の違い
親はprototypeの参照先を書き換えてしまう方法で、子はDumpCar.prototype.xxx にいちいち値やメソッドをつっこむ方法で定義してます。
親のような記述の方が見やすいわけですが、子でそれができないのは、
DumpCar.prototype = new Car(); /* 継承 */ DumpCar.prototype = { voice : 'GAAAA!', hasBed : 'yes'; init : function(p){ ... }
↑このような記述は、
DumpCar.prototype = new Car(); /* 継承 */ DumpCar.prototype = new Object; // あるいは DumpCar.prototype ={} DumpCar.prototype.voice = 'GAAAA!'; DumpCar.prototype.hasBed = 'yes'; DumpCar.prototype.init = function(p){ ...
↑これと同じ事を意味するので、new Car()で生成されたobjectがprototypeプロパティから参照できなくなってしまうため。
解決方法1
prototype.jsのextendような汎用ルーチンを使うと見通しの良い記述ができます。var extend = function(target,obj){ for(var i in obj){ target[i]=obj[i]; } } ... /* 子 */ var DumpCar = function(){} DumpCar.prototype = new Car(); /* 継承 */ extend(DumpCar.prototype,{ voice : 'GAAAA!', hasBed : 'yes'; init : function(p){ ... });
解決方法2
あるいは無名関数を使って以下のようにもできます。var DumpCar = (function(f,ext){ var c = (f.prototype = new ext); c.voice = 'GAAAA!'; c.hasBed = 'yes'; c.init = function(p){ ... } return f; })(function(){},Car) //親の定義でも使用する場合は、})(function(){},Object)とすればOK
インスタンス化とコンストラクタの実行
new で実体化した後、initで初期化処理を行うという冗長な記述になってます。
/* 実行 */ ((new DumpCar()).init({tire:6}).showSpec())
本来、以下の書き方で済むようにしたいところです。
((new DumpCar({tire:6})).showSpec())
これは、コンストラクタ内( var Car = function(){/*ここ*/} ) に、init と同様の処理を記述するか、
init をthis.init.apply でコールすれば実現できますが、親の継承のnewでも親のinitが実行されてしまうという問題が発生してしまいます。
/* 親 */ var Car = function(){this.init.apply(this,arguments)}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
解決方法1
継承で new を使うためにinit が実行されてしまうので、前述のextendような処理で継承をすればこれを抑止できます。
var extend = function(target,obj){ for(var i in obj){ target[i]=obj[i]; } } ... /* 子 */ var DumpCar = function(){this.init.apply(this,arguments)} extend(DumpCar.prototype,Car.prototype); /* 継承 */ extend(DumpCar.prototype,{ voice : 'GAAAA!', hasBed : 'yes'; init : function(p){ ... });
但しこの場合は、prototypeプロパティのobjectの書き換えを行ってないため、
インスタンス化されたobjectのconstructorプロパティのobjectが変わっていません。
必要に応じてconstractorの書き換えを行います。
/* new で継承した場合 */ DumpCar.prototype = new Car();/* 継承 */ ... alert((new DumpCar).constructor.prototype.tire) //4
/* extend関数 で継承した場合 */ extend(DumpCar.prototype,Car.prototype);/* 継承 */ ... alert((new DumpCar).constructor.prototype.tire) //6 /* constructorの書き換え */ DumpCar.prototype.constructor=Car alert((new DumpCar).constructor.prototype.tire) //4
解決方法2
他の空functionを間借りすることで解決することもできます
var DumpCar = function(){this.init.apply(this,arguments)} var f=function(){}; f.prototype = Car.prototype; DumpCar.prototype = new f(); /* fはinitを実行してないので継承だけが行われる*/
解決方法3
jQueryにように、new 指定なしでインスタンス化をするなら instanceof を活用することでも解決できます。
(new 指定した場合は initは起動されません)
/* 親 */ var Car = function(){ if(!(this instanceof Car))this.init.apply(this,arguments) } ・・・ /* 子 */ var DumpCar = function(){ if(!(this instanceof Car))this.init.apply(this,arguments) } DumpCar.prototype = new Car(); /* 継承 initは起動しない */ ・・・ /* 実行 */ DumpCar({tire:6}) //もしくは、new DumpCar().init({tire:6})