latest log

酩酊状態で書いたエンジニアポエムです。酩酊状態で読んでください。

mofmof.js とクラスベースOOP

クラスの使い方(初級)

mofmof.js では mm("Hoge", { メソッド }) でクラス mm.Hoge を定義することができます。

mm("Hoge", {

    init: function(arg1, arg2) {
        this._arg1 = arg1;
        this._arg2 = arg2;
    },

    method: function() {
        alert(this._arg1 + this._arg2);
    }
});

var hoge = new mm.Hoge(1, 2);

hoge.method();



new mm.Hoge(1, 2) のタイミングで init(1, 2) が呼ばれます
init はコンストラクタと呼ばれ、
主に初期化に関わる処理を記述します







mm.Hoge クラスのインスタンスを生成します

alert(3) が実行されます

クラス内部で使うプライベートなメソッドやプロパティは、先頭に "_" を付けパブリックなものとは区別します。

mm("Hoge", {
    _prop: "プライベートプロパティ",
    _method: function() {
        console.log("プライベートメソッド");
    }
});

クラスの使い方(中級)

Constructor and Destructor (コンストラクタとデストラクタ)

mm("Hoge", {
    init: function(arg1, arg2) {
        console.log("Hoge#init");
    },
    gc: function() {
        console.log("Hoge#gc");
    }
});

var hoge = new mm.Hoge(123, 456);

hoge.gc(); // 内部リソースは全て破棄されます

// [注意]
hoge.gc(); // 各インスタンスで gc は1度しか呼べません。
           // 2回目の gc 呼び出しでは、本来の gc とは別のダミー関数(GCSentinel)が呼ばれ、
           // コンソールに GC_BAD_CALL を出力します(エラーにはなりません)
Hoge#init
Hoge#gc
GC_BAD_CALL 

init はコンストラクタ(初期化メソッド)として機能します。引数をいくつでも指定できます。

gc はデストラクタ(解放メソッド)として機能します。引数を渡しても無視されます。

gc を実行するとそのクラスが持つ全てのリソースを mm.clear(this) で破棄します*1
this に関連付けられていないリソースが存在する場合は、gc が呼ばれたタイミングで適切に解放してください。
gc は自動実行されません。ユーザが明示的に実行する必要があります。

gc の挙動(delete で全て削除する)が問題となる場合は、ユーザが独自のリソース解放メソッドを用意し、そちらを呼び出すようにしてください。

Inheritance (継承)

mm("Hoge:Base", {}); とすることで Base を継承する Hoge を定義できます。

Constructor Chain and Destructor Chain (コンストラクタチェーンとデストラクタチェーン)

コンストラクタは、Baseクラスのコンストラクタから先に呼ばれます。
Base#init が存在する場合は、まず Base#init が呼ばれ、そのあとで Hoge#init が呼ばれます。

デストラクタはその逆で、最後にBaseクラスのデストラクタが呼ばれます。
Base#gc が存在する場合は、まず Hoge#gc が呼ばれ、そのあとで Base#gc が呼ばれます。

mm("Base", {
    init: function() { console.log("Base#init"); },
    gc:   function() { console.log("Base#gc");   }
});
mm("Hoge:Base", {
    init: function() { console.log("Hoge#init"); },
    gc:   function() { console.log("Hoge#gc");   }
});


var hoge = new mm.Hoge(); // Base#init() -> Hoge#init()

hoge.gc();                // Hoge#gc()   -> Base#gc()
Base#init
Hoge#init
Hoge#gc
Base#gc 

Static Member (静的メンバー)

mm("Hoge", prototypeMember, staticMember) とすることで、
クラスをインスタンス化せずに利用できるスタティックなメソッドやプロパティを定義可能です。

mm("Hoge", {
    init: function() {
    }
}, {
    MY_NAME_IS: "uupaa"
});





mm.Hoge.MY_NAME_IS でアクセスできます

Traits (トレイト)

名前が広く知られている機能をクラスに付与する事ができます。

mm("Hoge:Singleton:SelfInit", {

    init: function() {
        console.log("何度生成しても一度しか呼ばれません");
    }

});

new mm.Hoge;

mm.Hoge はシングルトンクラスになります

シングルトンクラスは、
何度 new しても実体が最大で1つしか生成されず、
init メソッドは最初の一度しか呼ばれません。




Traits and Inheritance (トレイトと継承)

Traits と継承は同時に指定可能です。

先ほどのコードで "Singleton:SelfInit" と指定していた部分が Traits になります。

Singleton はシングルトンクラスを作成する Trait です。
Singleton が指定されているクラスは、自動的に引数無しでインスタンス化(mm.iHoge = new mm.Hoge())が行われ、mm.iクラス名 にインスタンスが保存されます。

SelfInit は Singleton と同時に指定可能な Trait で、インスタンスの自動生成を抑止します。
シングルトンクラスのインスタンスを生成するタイミングをユーザが制御する場合に指定します。

mm("Base1", {});
mm("Base2:Base1", {});
mm("Base3:Base2", {});

mm("Hoge:Singleton:Base3", {
    method: function() {
        console.log("YES mm.iHoge");
    }
});

mm.iHoge.method();





mm.iHoge = new mm.Hoge;





"YES mm.iHoge"

クラスの使い方(上級)

Call Super Method (スーパーメソッドの呼び出し)

本来ならオーバーライドされ、隠されてしまうBaseクラスのメソッドを callSuper(メソッド名, 引数...) で呼び出すことが可能です。
指定されたメソッド名が存在しない場合は、 trap(メソッド名, 引数...) が呼ばれます。

mm("Base", {
    log: function(arg1, arg2) {
        console.log(arg1, arg2);
    },
    trap: function(methodName, arg) {
        console.log("CALL TRAP: method = " +
                    methodName);
    }
});
mm("Hoge:Base", {
    log: function(arg1, arg2, arg3) {
        this.callSuper("log", arg1, arg2 + arg3);
    },
    typo: function(arg) {
        this.callSuper("typo", arg);
    }
});

var hoge = new mm.Hoge;

hoge.log(1, 2, 3)

hoge.typo(123);





















1 5

Base#trap が呼ばれます
"CALL TRAP: method = typo"

Shared Objects (オブジェクトの共有)

prototypeMember にオブジェクトを指定すると各クラスのインスタンスで共有されます。
prototypeMember に関数を指定すると、それが各インスタンスでメソッドとして共有されるように、Object(Hash) や Array も各インスタンス間で共有されます。このような特性を認識した上で、ObjectやArrayを使わないとバグを生み出してしまいます。

反対に、プリミティブ値(数値, 文字列, 真偽値, null, undefined)は各インスタンス間で共有される事はありません。

mm("Hoge", {
    myArray: [],
    myString: "",
    init: function(ary, str) {
        this.myArray.push(ary);
        this.myString = str;
    }
});

var hoge1 = new mm.Hoge(1, "hoge1");
var hoge2 = new mm.Hoge(2, "hoge2");

console.log(hoge1.myArray + "");
console.log(hoge1.myString);


myArray は hoge1 と hoge2 で共有されてしまう
myString は hoge1 と hoge2 で共有されない









1,2     (共有されてしまっている)
hoge1   (共有されていない)

Reserved name (予約語)

以下は mofmof.js 内部で利用している予約語です。ユーザが独自の意味を割り当てる事はできません。

クラスの予約済みメソッド名: init, gc, trap, callSuper

クラスの予約済みプロパティ: __instance__, __CLASS_NAME__, __INSTANCE_ID__, __BASE__

クラスの予約済み Traits名: Singleton, SelfInit

Messageing (メッセージング)

msgbox メソッドを定義し、initメソッドで、mm.msg.{メッセージグループ}.bind(this) とすると、msgboxメソッドでメッセージを受信可能になります。

var mm.msg.MyMessageGroup = new mm.Msg;

mm("Hoge:Singleton:AudoInit", {
    init: function() {
        mm.msg.MyMessageGroup.bind(this);
    },
    msgbox: function(msg, arg) {
        console.log(msg); // HELLO WORLD
    }
});

mm.msg.MyMessageGroup.to(mm.iHoge).post("HELLO WORLD", { msg: "なんでも渡せます" });

DOM Event Handler (handleEvent)

handleEvent メソッドを定義し、addEventListener の第二引数にクラスのインスタンス(this)を指定すると、DOMイベント発生時に handleEvent メソッドが呼び出されます。

mm("Hoge:Singleton:AudoInit", {
    init: function() {
        document.addEventListener("DOMContentLoaded", this);
    },
    handleEvent: function(event) {
        console.log(event.type); // "DOMContentLoaded"
    }
});

*1:mm.clear で this に関連付けられているリソースを全て delete します