自分用class定義ライブラリmyclass.jsを作ってみる(2)
前回の続きということで...
自分用class定義ライブラリmyclass.js作ってみた
prototype.jsのv1.6.0っぽく書けるようにしてみました。とりあえずこれを自分用class定義のベースにして、自作jQueryプラグインとかとからめて使ってこうかと思ってます。
使用例
var Car = MyClass.create({ 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 = MyClass.create(Car,{ voice : 'GAAAA!', hasBed : 'yes', sts :{ drive : 'yes' }, init : function(p){ this.$super.init(p) return this; } }); var myDump = DumyCar({tire:6}).showSpec(); /* もしくは */ var myDump = (new DumyCar({tire:6})).showSpec();
MyClass.createに引数を2つ渡した場合、第一引数が親オブジェクトになります。使用する際は、var myDump = DumyCar(.. のように new なしでもインスタンス化されます。
MyClassのソース
var MyClass = { Util : { extend : function(target,obj){ for(var i in obj){ target[i] = obj[i]; } return target; }, getInstance : function(obj,deep){ if(!deep){ var f = function(){}; f.prototype = obj; return new f; } else if (obj.constructor === Object) { var f = function(){}; for(var i in obj){ f.prototype[i] = MyClass.Util.getInstance(obj[i],deep) } return new f(); } else{ return obj; } }, resetDeepInstanceProperty : function(ins){ for(var i in ins){ if(ins[i].constructor === Object) ins[i]=MyClass.Util.getInstance(ins[i],true); } } }, create : function(e,m){ var member = m?m:e; var ext = m?e:null; var cns = function(){ var ins = this; if (!(ins instanceof cns)) { ins = MyClass.Util.getInstance(cns.prototype); } MyClass.Util.resetDeepInstanceProperty(ins); if(ins.init)ins.init.apply(ins,arguments); return ins; } if(ext){ cns.prototype = MyClass.Util.getInstance(ext.prototype); cns.prototype.$super = ext.prototype; } MyClass.Util.extend(cns.prototype,member); return cns; } }
実質的には、Utilオブジェクトはextendのみで事足りるのですが、後述する「ネストしたオブジェクトのインスタンス化」のため処理が増えてます。
親コンストラクタの初期化処理の呼び出し
前回のエントリで解決してなかった、親コンストラクタの初期化処理を this.$super.init() で呼び出し可能にしてます。
this.$super.init()の実行で問題が発覚しました。詳細は次のエントリを参照ください。
var DumpCar = MyClass.create(Car,{ voice : 'GAAAA!', hasBed : 'yes', sts :{ drive : 'yes' }, init : function(p){ /* $superで親を参照できます */ this.$super.init(p) return this; } });
内部的には継承用にnew された親インスタンスのprototypeプロパティに、親オブジェクトのprototypeを指す$superプロパティを追加することで、子から親の参照を可能にしてます。
var MyClass = { create : function(e,m){ ... if(ext){ /* インスタンス化した親オブジェクトのprototypeを$superに格納*/ cns.prototype = MyClass.Util.getInstance(ext.prototype); cns.prototype.$super = ext.prototype; } ... } }
ネストしたオブジェクトのインスタンス化
UI系の汎用ルーチンを作成してると状態を管理するプロパティが増えてきて、以下のstsのように入れ子で管理したくなる場合があります。
var DumpCar = MyClass.create(Car,{ voice : 'GAAAA!', hasBed : 'yes', sts :{ drive : 'yes', aaa:1, bbb:2, ... }, init : function(p){ this.$super.init(p) return this; } });
単純なnew による継承(MyClass.createの第2引数部のオブジェクトのみのnew)では、stsオブジェクトの新たなインスタンスの生成は行われないので、sts.driveの値を変更すると、おおもとのオブジェクトのprototype(DumyCarクラスのsts.drive)の値が書き換わってしまうので、以下処理にて子孫オブジェクトにオブジェクト(FunctionやArrayでないJSON形式のオブジェクト)があった場合、インスタンスを生成するようにしています。
var MyClass = { Util : { ... getInstance : function(obj,deep){ if(!deep){ ... } else if (obj.constructor === Object) { var f = function(){}; for(var i in obj){ f.prototype[i] = MyClass.Util.getInstance(obj[i],deep) } return new f(); } else{ return obj; } }, resetDeepInstanceProperty : function(ins){ for(var i in ins){ if(ins[i].constructor === Object)ins[i]=MyClass.Util.getInstance(ins[i],true); } } }, create : function(e,m){ ... var cns = function(){ ... /* ネストされたobjectの定義も実体化する */ MyClass.Util.resetDeepInstanceProperty(ins); ... } ... } }
参考にさせていただいた記事
以下の記事を参考にさせていただきました。ありがとうございました。