[jQuery plugin] Plugin 的思路與方法

寫 Javascript 一直都不是我的強項,所以當 jQuery 一推出的時候,簡直是驚為天人!這麼好用的東西為什麼現在才會出現呢?那我在 1998 年寫得那些,不就是跟垃圾一樣了嘛(眼神死

所以回過頭來,研究一下 Plugin 也是很合理的。


Plugin 該怎麼開始

通常我們看到的範例會這樣寫,

(function($) {

    var defaults = {};

    $.fn.myplugin = function() {

        return $(this).each(function() {

        });
    };

})(jQuery);

然後呢,我們就在 return... 的區塊裡面,開始寫我們要用的東西。這樣的寫法有個缺點,就是 this 的問題會讓你很頭痛。另外,當我們想要對這個 Plugin 作一些擴充動作的時候,這樣也會衍生出一些不必要得麻煩。

那我們還可以怎麼做?

(function($) {

    var defaults = {};

    var methods = {

        myMethodOne: function my_method_one() {},
        myMethodTwo: function my_method_two() {}

    };

    $.fn.myplugin = function() {

        return $(this).each(function() {

        });
    };

})(jQuery);

另外一種思考的方式是,我們可以定義一個叫做 methods 的物件,然後把我們所需要的 方法 都打包起來,同樣的,你還是會遇到 this 的問題。但是,相較之下,這樣的作法對於往後的擴充性來說,多少會有點幫助。

那還有其他的方式嗎?

(function($) {

    $.myplugin = function myplugin() {
    };

    $.myplugin.prototype = {

    };

    $.fn.myplugin = function() {

    };

})(jQuery);

這是一種利用 Javascript 的原生 prototype 的方式來打包,好處是,擴充性足,而且也可以利用一些巧妙的方法,避開 this 衍生的問題。當然,這些方法並不是非常完美的解法,但是已敷使用。

什麼是 this 的問題

其實牽扯到 this 總是會有講不完的東西,要詳細解釋實在有點惱人。最簡單,淺顯易懂的例子,大概就是,

$('#container').each(function() {
    var instance = this; // 這裡的 this 意指 $('#container')

    var myObj = new myObject();

    myObj.mySimpleFunc(function() {
        var instance = this; // 這裡的 this 是 myObj
    });

    $(this).children().eq(0).bind('click', function(event) {
        var instance = this; // 這裡的 this 是 $('#container').children().eq(0)
    }, false);
});

雖然他是很奇妙又好用的東西,但是東西一旦多起來,就會讓人覺得又愛又恨了。所以說,jQuery 雖然有個叫做 $.proxy 的東西,可以用來避免這些事情,但是,其實我從頭到尾都沒仔細去弄懂,$.proxy 他到底在幹麼(眼神死

包裝你的 Plugin

形式有很多,但是我這裡想說的是 prototype 這個方法。原因很簡單,第一,擴充性強,第二,我可以避開 this 的問題。光就這兩點來說,我就可以省去很多麻煩了。

所以,我們一樣從頭開始,

(function(window, $, undefined) {

    $.myplugin = function myplugin(options, callback, element) {
        this.element = $(element);
        this._init(options, callback);
    };

    $.myplugin.defaults = {
        page: 1
    };

    $.myplugin.prototype = {

        _init: function myplugin_init(options, callback) {
            var instance = this, opts = this.options = $.extend(true, {}, $.myplugin.defaults, options);

            // 開始做事

            // 最後回呼,如果你不要就把 callback 拿掉就好
            if($.isFunction(callback)) callback(this);
        },

        /*
         * 這是一個公開方法的範例,用於更新 options 設定值
         * 使用方式是 $('main').myplugin('update', {page: 3});
         * 要留意的是,呼叫方法並不需要回呼的動作
         * 當然你要也可以,只是看起來很假會
         */
        update: function myplugin_update(options) {
            var options = options || {};
            if ($.isPlainObject(options)) {
                this.options = $.extend(true, {}, this.options, options);
            }
        }

    };

    $.fn.myplugin = function(options, callback) {

        var thisCall = typeof options;

        switch(thisCall) {
            // method
            case 'string':
                var args = Array.prototype.slice.call(arguments, 1);

                this.each(function() {
                    var instance = $.data(this, 'myplugin');

                    if (!instance) {
                        return false;
                    }
                    // 我們使用 _ 開頭的函式來當作私有函式,所以不允許由外部呼叫
                    if (!$.isFunction(instance[options]) || options.charAt(0) === "_") {
                        return false;
                    }

                    instance[options].apply(instance, args);
                });
            break;

            // creation
            case 'object':
                this.each(function() {
                    var instance = $.data(this, 'myplugin');

                    if (instance) {
                        instance.update(options);
                    } else {
                        // 我們透過 new 來動態建立一個我們所寫好的 prototype
                        // 並且將他利用 $.data 的方式儲存起來
                        // 好處是,我們隨時都可以用 $.data 把他拿出來作壞事
                        $.data(this, 'myplugin', new $.myplugin(options, callback, this));
                    }
                });
            break;
        }

        return this;

    };

})(window, jQuery);

這個看似複雜的包裝方法,其實實作起來並沒有想像中的困難。而且我已經順利的將之前寫過的 Plugin,以這種方式重新打包,運作也相當順利。只是,我不知道這種方式,和原先 jQuery 的傳統方法,哪一個效能會比較好,抑或是哪一個會比較不吃瀏覽器記憶體。

我不知道該怎麼去檢測這些事情,所以就隨他去吧(不負責任貌

結語

沒事不要學人家寫 Plugin

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei