jQueryプラグインをうまいことカプセル化する設計方法その2

jQueryの次期バージョンの1.5ではjQuery自体のサブクラスを生成できるらしい。

なので、jQueryプラグインをうまいことカプセル化する方法として、プラグイン毎にjQueryサブクラスを用意するという選択肢が増えた。あんまり検証していないけど。

プラグインのソースもAPIもシンプルでいいと思うけど、パフォーマンス的にはどうなんだろう。

[2011.02.02追記] subclass()はjQuery1.5のリリースでsub()に変更になったようです

/**
 * jquery.myplug サブクラス方式
 *
 * SYNOPSIS
 *
 * $.myplug("target").method1();
 * $.myplug("target").method2();
 * $.myplug("target").method1().method2();
 */
;(function($) {

    /**
     * プラグインの名称
     */
    var plugname = 'myplug';
    $[plugname] = $.sub(); // [2011.02.02]subclass() -> sub()に変更
    $[plugname].fn.method1 = function(params) {return this}
    $[plugname].fn.method2 = function(params) {return this}
})(jQuery);

[追記2011.1.18][更新2011.1.18]

ベンチマークとってみた。jQueryプラグインをうまいことカプセル化する設計方法で構造的には一番合理的と思われたインナークラス方式と今回のサブクラス方式、公式ドキュメントに紹介されてる方式を比較。結果は初期化についてはサブクラス方式のほうが8〜10倍速かった。メソッドコールについてはほぼ差はなし。

  インナークラス サブクラス 公式ドキュメント
ロード 100.00(483.20ms) 422.72(2042.60ms) 97.14(469.40ms)
インスタンス化 100.00(670.60ms) 10.47(70.20ms) 8.71(58.40ms)
メソッドコール 100.00(2434.20ms) 99.17(2414.00ms) 215.37(5242.60ms)
ロード&インスタンス化 100.00(1483.80ms) 26.84(398.20ms) 11.83(175.60ms)

 

ベンチマークは下記のスクリプトにて。計測はFirefoxのみで行った。

// ベンチマーク
function bench(funcs, num1, num2) {
    function a(func) {
        var t1 = new Date();
        for (var i = 0; i < num1; i++) {
            func();
        }
        return new Date() - t1;
    }
    var res = {};
    for (var i = 0; i < num2; i++) {
        for (var idx in funcs) {
            res[idx] = (res[idx] || []);
            res[idx].push(a(funcs[idx]));
        }
    }
    var aves = [];
    for (var idx in res) {
        var sum = 0;
        for (i = 0; i < res[idx].length; i++) {
            sum = sum + res[idx][i];
        }
        aves.push(sum / res[idx].length);
    }
    for (var i in aves) {
        console.log((+i + 1) + ': '
                    + (aves[i] * 100 / aves[0]).toFixed(2)
                    + '(' + aves[i].toFixed(2) + 'ms)');
    }
}

// サブクラス方式ロード
var subClassLoad = function() {
    ;(function($) {
        var plugname = 'myplug2';
        $[plugname] = $.subclass();
        var default_params = {};
        $[plugname].fn.pluginit = function(params) {
            $.extend(default_params, params, {})
        }
        $[plugname].fn.method1 = function(params) {return 'myplug1 a'}
        $[plugname].fn.method2 = function(params) {return 'myplug2 b'}
    })(jQuery);
}
// インナークラス方式ロード
var innerClassLoad = function(){
    ;(function($) {
        var plugname = 'myplug';
        var Class = function(elem, params){
            this.elem = elem;
            this.elem.data(plugname, this);
            this.params = params;
        }
        Class.prototype.method1 = function(params){
            return 'myplug a';
        };
        Class.prototype.method2 = function(params) {
            return 'myplug b';
        };
        var default_params = {};
        $.fn[plugname] = function(params){
            return new Class(this, $.extend(default_params, params, {}));
        }
    })(jQuery);
}
// サブクラス方式ロード
var formalLoad = function(){
    ;(function($) {
        var plugname = 'myplug3';
        var methods = {
            init    : function(params){return this;},
            method1 : function(params){return 'myplug3 a';},
            method2 : function(params) {return 'myplug3 b';}
        };
        $.fn[plugname] = function(method) {
            if ( methods[method] ) {
                return methods[ method ]
                    .apply( this,
                           Array.prototype.slice.call( arguments, 1 ));
            } else if ( typeof method === 'object' || ! method ) {
                return methods.init.apply( this, arguments );
            } else {
                $.error( 'Method ' +  method
                        + ' does not exist on jQuery.' + plugname );
                return this;
            }
        };
    })(jQuery);
}

innerClassLoad();
subClassLoad();
formalLoad();

// インナークラス方式インスタンス化
var innerClassInit = function() {var a = $("#target1").myplug();}
// サブクラス方式インスタンス
var subClassInit = function(){var b = $.myplug2("#target1");}
// 公式ドキュメント方式インスタンス
var formalInit = function(){var c = $("#target1");};
// インナークラス方式メソッドコール
var a = $("#target1").myplug();
var innerClassMethodCall = function() {a.method1();};
// サブクラス方式メソッドコール
var b = $.myplug2("#target1");
var subClassMethodCall = function(){b.method1();};
// 公式ドキュメント方式メソッドコール
var c = $("#target1");
var formalMethodCall = function(){c.myplug3('method1');};
// サブクラス方式ロード&インスタンス
var subClassLoadAndInit = function() {
    subClassLoad();
    subClassInit();
};
// インナークラス方式ロード&インスタンス
var innerClassLoadAndInit = function() {
    innerClassLoad();
    innerClassInit();
}
// 公式ドキュメント方式ロード&初期化
var formalLoadAndInit = function() {
    formalLoad();
    formalInit();
};
bench([innerClassLoad, subClassLoad, formalLoad], 100000, 5);
bench([innerClassInit, subClassInit, formalInit], 5000, 5);
bench([innerClassMethodCall, subClassMethodCall, formalMethodCall], 1000000, 5);
bench([innerClassLoadAndInit, subClassLoadAndInit, formalLoadAndInit], 10000, 5);

 

jQueryプラグインをうまいことカプセル化する設計方法その2」への3件のフィードバック

  1. ピンバック: jQueryプラグインをうまいことカプセル化する設計方法- jamadam weblog2

  2. ピンバック: jquery.customEventTrigger- jamadam weblog2

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です