自分用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);
            ...
        }
        ...
    }
}

参考にさせていただいた記事

以下の記事を参考にさせていただきました。ありがとうございました。