天天看點

jQuery插件開發小總結

另一篇

jQuery插件開發通常有3種方式

  • 通過$.extend()來擴充jQuery
  • 通過$.fn 向jQuery添加新的方法
  • 通過$.widget()應用jQuery UI的部件工廠方式建立

通常我們使用第二種方法來進行簡單插件開發,說簡單是相對于第三種方式。第三種方式是用來開發更進階jQuery部件的,該模式開發出來的部件帶有很多jQuery内建的特性,比如插件的狀态資訊自動儲存,各種關于插件的常用方法等,非常貼心,這裡不細說.

而第一種方式又太簡單,僅僅是在jQuery命名空間或者了解成jQuery身上添加了一個靜态方法而以。是以我們調用通過$.extend()添加的函數時直接通過$符号調用($.myfunction())而不需要選中DOM元素($('#example').myfunction())。請看下面的例子。

$.extend({
    sayHello: function(name) {
        console.log('Hello,' + (name ? name : 'Dude') + '!');
    }
})
$.sayHello(); //調用
$.sayHello('Wayou'); //帶參調用
                   

上面代碼中,通過$.extend()向jQuery添加了一個sayHello函數,然後通過$直接調用。到此你可以認為我們已經完成了一個簡單的jQuery插件了。 但如你所見,這種方式用來定義一些輔助方法是比較友善的。比如一個自定義的console,輸出特定格式的資訊,定義一次後可以通過jQuery在程式中任何需要的地方調用它。

$(function () {
        $.log('hahha');
    })
    $.extend({
        log: function (message) {
            var now = new Date();
            y = now.getFullYear(),
              m = now.getMonth() + 1, //!JavaScript中月分是從0開始的
              d = now.getDate(),
              h = now.getHours(),
              min = now.getMinutes(),
              s = now.getSeconds(),
             time = y + '-' + m + '-' + d + ' ' + h + ':' + min + ':' + s;
            console.log(time + ' 輸出: ' + message);
        }
    })
                    

但這種方式無法利用jQuery強大的選擇器帶來的便利,要處理DOM元素以及将插件更好地運用于所選擇的元素身上,還是需要使用第二種開發方式。你所見到或使用的插件也大多是通過此種方式開發。

插件開發

基本方法

先看一下它的基本格式:

$.fn.pluginName = function() {
           //your code goes here
        }
                       

基本上就是往$.fn上面添加一個方法,名字是我們的插件名稱。然後我們的插件代碼在這個方法裡面展開。 比如我們将頁面上所有連結顔色轉成紅色,則可以這樣寫這個插件:

$.fn.myPlugin = function() {
      //在這裡面,this指的是用jQuery選中的元素
      //example :$('a'),則this=$('a')
     this.css('color', 'red');
}

//使用
    $(function(){
		$('a').myPlugin();
	})
                       

在插件名字定義的這個函數内部,this指代的是我們在調用該插件時,用jQuery選擇器選中的元素,一般是一個jQuery類型的集合。比如$('a')傳回的是頁面上所有a标簽的集合,且這個集合已經是jQuery包裝類型了,也就是說,在對其進行操作的時候可以直接調用jQuery的其他方法而不需要再用美元符号來包裝一下。是以在上面插件代碼中,我們在this身上調用jQuery的css()方法,也就相當于在調用 $('a').css()。了解this在這個地方的含義很重要。這樣你才知道為什麼可以直接商用jQuery方法同時在其他地方this指代不同時我們又需要用jQuery重新包裝才能調用,下面會講到。初學容易被this的值整暈,但了解了就不難。

下面進一步,在插件代碼裡處理每個具體的元素,而不是對一個集合進行處理,這樣我們就可以針對每個元素進行相應操作。 我們已經知道this指代jQuery選擇器傳回的集合,那麼通過調用jQuery的.each()方法就可以處理合集中的每個元素了,但此刻要注意的是,在each方法内部,this指帶的是普通的DOM元素了,如果需要調用jQuery的方法那就需要用$來重新包裝一下。

$.fn.myPlugin = function() {
    //在這裡面,this指的是用jQuery選中的元素
    this.css('color', 'red');
    this.each(function() {
        //對每個元素進行操作
        $(this).append(' ' + $(this).attr('href'));
    }))
}
                       

支援鍊式調用

要讓插件不打破這種鍊式調用,隻需return一下即可。

$.fn.myPlugin = function() {
    //在這裡面,this指的是用jQuery選中的元素
    this.css('color', 'red');
    return this.each(function() {
        //對每個元素進行操作
        $(this).append(' ' + $(this).attr('href'));
    }))
}
                       

讓插件接收參數

一個強勁的插件是可以讓使用者随意定制的,這要求我們提供在編寫插件時就要考慮得全面些,盡量提供合适的參數。比如現在我們不想讓連結隻變成紅色,我們讓插件的使用者自己定義顯示什麼顔色,要做到這一點很友善,隻需要使用者在調用的時候傳入一個參數即可。同時我們在插件的代碼裡面接收。另一方面,為了靈活,使用者可以不傳遞參數,插件裡面會給出參數的預設值。在處理插件參數的接收上,通常使用jQuery的extend方法,上面也提到過,但那是給extend方法傳遞單個對象的情況下,這個對象會合并到jQuery身上,是以我們就可以在jQuery身上調用新合并對象裡包含的方法了,像上面的例子。當給extend方法傳遞一個以上的參數時,它會将所有參數對象合并到第一個裡。同時,如果對象中有同名屬性時,合并的時候後面的會覆寫前面的.利用這一點,我們可以在插件裡定義一個儲存插件參數預設值的對象,同時将接收來的參數對象合并到預設對象上,最後就實作了使用者指定了值的參數使用指定的值,未指定的參數使用插件預設值。

$.fn.myPlugin()=function(options)
    {
        var defaults={
            'color':'red',
            'fontSize':'12px'
        };
        var settings = $.extend(defaults, options);
        return this.css({
            'color': settings.color,
            'fontSize':settings.fontSize
        })
    }

                
//調用
$('a').myPlugin({
    'color': '#2C9929'
});
                       

注意到上面代碼調用extend時會将defaults的值改變,這樣不好,因為它作為插件因有的一些東西應該維持原樣,另外就是如果你在後續代碼中還要使用這些預設值的話,當你再次通路它時它已經被使用者傳進來的參數更改了。

保護好預設參數

一個好的做法是将一個新的空對象做為$.extend的第一個參數,defaults和使用者傳遞的參數對象緊随其後,這樣做的好處是所有值被合并到這個空對象上,保護了插件裡面的預設值。建議在寫JQuery插件時,定義預設屬性時,都用defaults變量來代表預設屬性,這樣的代碼更具有可讀性。

var settings = $.extend({},defaults, options);//将一個空對象做為第一個參數
                       

到此,插件可以接收和處理參數後,就可以編寫出更健壯而靈活的插件了。若要編寫一個複雜的插件,代碼量會很大,如何組織代碼就成了一個需要面臨的問題,沒有一個好的方式來組織這些代碼,整體感覺會雜亂無章,同時也不好維護,是以将插件的所有方法屬性包裝到一個對象上,用面向對象的思維來進行開發,無疑會使工作輕松很多

jQuery.fn.extend()和jQuery.extend()

jQuery.extend(object) ,一個參數的用于擴充jQuery類本身,也就是用來在jQuery類/命名空間上增加新函數,或者叫靜态方法,例如jQuery内置的 ajax方法都是用jQuery.ajax()這樣調用的,有點像 “類名.方法名” 靜态方法的調用方式。下面我們也來寫個jQuery.extend(object)的例子:

//擴充jQuery對象本身
        jQuery.extend({
            "minValue": function (a, b) {
                ///<summary>
                /// 比較兩個值,傳回最小值
                ///</summary>
                return a < b ? a : b;
            },
            "maxValue": function (a, b) {
                ///<summary>
                /// 比較兩個值,傳回最大值
                ///</summary>
                return a > b ? a : b;
            }
        });
        //調用
        var i = 100; j = 101;
        var min_v = $.minValue(i, j); // min_v 等于 100
        var max_v = $.maxValue(i, j); // max_v 等于 101
                       

jQuery.fn.extend(object)擴充 jQuery 元素集來提供新的方法(通常用來制作插件)。首先我們來看fn 是什麼東西呢。檢視jQuery代碼,就不難發現。

jQuery.fn = jQuery.prototype = {
   init: function( selector, context ) {.....};
};
                       

原來 jQuery.fn = jQuery.prototype,也就是jQuery對象的原型。那jQuery.fn.extend()方法就是擴充jQuery對象的原型方法。我們知道擴充原型上的方法,就相當于為對象添加”成員方法“,類的”成員方法“要類的對象才能調用,是以使用jQuery.fn.extend(object)擴充的方法, jQuery類的執行個體可以使用這個“成員函數”。

面向對象的插件開發

為什麼要有面向對象的思維,因為如果不這樣,你可能需要一個方法的時候就去定義一個function,當需要另外一個方法的時候,再去随便定義一個function,同樣,需要一個變量的時候,毫無規則地定義一些散落在代碼各處的變量。

如果将需要的重要變量定義到對象的屬性上,函數變成對象的方法,當我們需要的時候通過對象來擷取,一來友善管理,二來不會影響外部命名空間,因為所有這些變量名還有方法名都是在對象内部。

var Beautifier = function (ele,opt)
    {
        this.$element = ele;
        this.defaults = {
            'color': 'red',
            'fontSize': '12px',
            'textDecoration':'none'
        }
        this.options = $.extend({}, this.defaults, opt);
    }

    Beautifier.prototype = {
        beautify: function () {
            return this.$element.css({
                'color': this.options.color,
                'fontSize': this.options.fontSize,
                'textDecoration': this.options.fontSize,
            })
        }
    }

    $.fn.myOther = function (options)
    {
        var beautifier = new Beautifier(this, options);
        return beautifier.beautify();
    }
                       

通過上面這樣一改造,我們的代碼變得更面向對象了,也更好維護和了解,以後要加新功能新方法,隻需向對象添加新變量及方法即可,然後在插件裡執行個體化後即可調用新添加的東西。插件的調用還是一樣的,我們對代碼的改動并不影響插件其他地方,隻是将代碼的組織結構改動了而以。

$(function() {
    $('a').myPlugin({
        'color': '#2C9929',
        'fontSize': '20px'
    });
})
                       

到這裡,你可以更好地編寫複雜的插件同時很好地組織代碼了。當我們回頭去看上面的代碼時,其實也還是有改進空間的。也就是下面介紹的關于命名空間及變量各什麼的,一些雜項。

關于命名空間

不僅僅是jQuery插件的開發,我們在寫任何JS代碼時都應該注意的一點是不要污染全局命名空間。因為随着你代碼的增多,如果有意無意在全局範圍内定義一些變量的話,最後很難維護,也容易跟别人寫的代碼有沖突。比如你在代碼中向全局window對象添加了一個變量status用于存放狀态,同時頁面中引用了另一個别人寫的庫,也向全局添加了這樣一個同名變量,最後的結果肯定不是你想要的。是以不到萬不得已,一般我們不會将變量定義成全局的。

一個好的做法是始終用自調用匿名函數包裹你的代碼,這樣就可以完全放心,安全地将它用于任何地方了,絕對沒有沖突。

我們知道JavaScript中無法用花括号友善地建立作用域,但函數卻可以形成一個作用域,域内的代碼是無法被外界通路的。如果我們将自己的代碼放入一個函數中,那麼就不會污染全局命名空間,同時不會和别的代碼沖突

如上面我們定義了一個Beautifier全局變量,它會被附到全局的window對象上,為了防止這種事情發生,你或許會說,把所有代碼放到jQuery的插件定義代碼裡面去啊,也就是放到$.fn.myPlugin裡面。這樣做倒也是種選擇。但會讓我們實際跟插件定義有關的代碼變得臃腫,而在$.fn.myPlugin裡面我們其實應該更專注于插件的調用,以及如何與jQuery互動。是以保持原來的代碼不變,我們将所有代碼用自調用匿名函數包裹。

(function() {
    //定義Beautifier的構造函數
    var Beautifier = function(ele, opt) {
        this.$element = ele,
        this.defaults = {
            'color': 'red',
            'fontSize': '12px',
            'textDecoration': 'none'
        },
        this.options = $.extend({}, this.defaults, opt)
    }
    //定義Beautifier的方法
    Beautifier.prototype = {
        beautify: function() {
            return this.$element.css({
                'color': this.options.color,
                'fontSize': this.options.fontSize,
                'textDecoration': this.options.textDecoration
            });
        }
    }
    //在插件中使用Beautifier對象
    $.fn.myPlugin = function(options) {
        //建立Beautifier的實體
        var beautifier = new Beautifier(this, options);
        //調用其方法
        return beautifier.beautify();
    }
})();
                       

這樣做的好處,也就是上面所闡述的那樣。另外還有一個好處就是,自調用匿名函數裡面的代碼會在第一時間執行,頁面準備好過後,上面的代碼就将插件準備好了,以友善在後面的代碼中使用插件。

目前為止似乎接近完美了。如果再考慮到其他一些因素,比如我們将這段代碼放到頁面後,前面别人寫的代碼沒有用分号結尾,或者前面的代碼将window, undefined等這些系統變量或者關鍵字修改掉了,正好我們又在自己的代碼裡面進行了使用,那結果也是不可預測的,這不是 我們想要的。

将系統變量以變量形式傳遞到插件内部

來看下面的代碼,你猜他會出現什麼結果?

var foo=function(){
    //别人的代碼
}//注意這裡沒有用分号結尾

//開始我們的代碼。。。
(function(){
    //我們的代碼。。
    alert('Hello!');
})();
                       

本來别人的代碼也正常工作,隻是最後定義的那個函數沒有用分号結尾而以,然後當頁面中引入我們的插件時,報錯了,我們的代碼無法正常執行。原因是我們用來充當自調用匿名函數的第一對括号與上面别人定義的函數相連,因為中間沒有分号嘛,總之我們的代碼無法正常解析了,是以報錯。是以好的做法是我們在代碼開頭加一個分号,這在任何時候都是一個好的習慣。

//開始我們的代碼。。。
;(function(){
    //我們的代碼。。
    alert('Hello!');
})();
                       

同時,将系統變量以參數形式傳遞到插件内部也是個不錯的實踐。當我們這樣做之後,window等系統變量在插件内部就有了一個局部的引用,可以提高通路速度,會有些許性能的提升。最後我們得到一個非常安全結構良好的代碼:

;(function($,window,document,undefined){
    //我們的代碼。。
    //blah blah blah...
})(jQuery,window,document);
                       

而至于這個undefined,稍微有意思一點,為了得到沒有被修改的undefined,我們并沒有傳遞這個參數,但卻在接收時接收了它,因為實際并沒有傳,是以‘undefined’那個位置接收到的就是真實的'undefined'了。是不是有點hack的味道,值得細細體會的技術

是以最後我們的插件成了這樣:

;(function($, window, document,undefined) {
    //定義Beautifier的構造函數
    var Beautifier = function(ele, opt) {
        this.$element = ele,
        this.defaults = {
            'color': 'red',
            'fontSize': '12px',
            'textDecoration': 'none'
        },
        this.options = $.extend({}, this.defaults, opt)
    }
    //定義Beautifier的方法
    Beautifier.prototype = {
        beautify: function() {
            return this.$element.css({
                'color': this.options.color,
                'fontSize': this.options.fontSize,
                'textDecoration': this.options.textDecoration
            });
        }
    }
    //在插件中使用Beautifier對象
    $.fn.myPlugin = function(options) {
        //建立Beautifier的實體
        var beautifier = new Beautifier(this, options);
        //調用其方法
        return beautifier.beautify();
    }
})(jQuery, window, document);
                       

有些時候,我們的插件需要一些私有方法,不能被外界通路。例如 我們插件裡面需要有個方法 來檢測使用者調用插件時傳入的參數是否符合規範。

//閉包限定命名空間
;(function ($) {
    $.fn.extend({
        "highLight": function (options) {
            //檢測使用者傳進來的參數是否合法
            if (!isValid(options))
                return this;
            var opts = $.extend({}, defaluts, options); //使用jQuery.extend 覆寫插件預設參數
            return this.each(function () {  //這裡的this 就是 jQuery對象。這裡return 為了支援鍊式調用
                //周遊所有的要高亮的dom,當調用 highLight()插件的是一個集合的時候。
                var $this = $(this); //擷取目前dom 的 jQuery對象,這裡的this是目前循環的dom
                //根據參數來設定 dom的樣式
                $this.css({
                    backgroundColor: opts.background,
                    color: opts.foreground
                });
                //格式化高亮文本
                var markup = $this.html();
                markup = $.fn.highLight.format(markup);
                $this.html(markup);
            });

        }
    });
    //預設參數
    var defaluts = {
        foreground: 'red',
        background: 'yellow'
    };
    //公共的格式化 方法. 預設是加粗,使用者可以通過覆寫該方法達到不同的格式化效果。
    $.fn.highLight.format = function (str) {
        return "" + str + "";
    }
    //私有方法,檢測參數是否合法
    function isValid(options) {
        return !options || (options && typeof options === "object") ? true : false;
    }
})(window.jQuery);
     


            //調用
        //調用者覆寫 插件暴露的共公方法
        $.fn.highLight.format = function (txt) {
            return  txt ;
        }
        $(function () {
            $("p").highLight({ foreground: 'orange', background: '#ccc' }); //調用自定義 高亮插件
        });
                       

關于變量定義及命名

變量定義:好的做法是把将要使用的變量名用一個var關鍵字一并定義在代碼開頭,變量名間用逗号隔開。原因有二:

  • 一是便于了解,知道下面的代碼會用到哪些變量,同時代碼顯得整潔且有規律,也友善管理,變量定義與邏輯代碼分開;
  • 二是因為JavaScript中所有變量及函數名會自動提升,也稱之為JavaScript的Hoist特性,即使你将變量的定義穿插在邏輯代碼中,在代碼解析運作期間,這些變量的聲明還是被提升到了目前作用域最頂端的,是以我們将變量定義在一個作用域的開頭是更符合邏輯的一種做法。當然,再次說明這隻是一種約定,不是必需的。

變量及函數命名 一般使用駝峰命名法(CamelCase),即首個單詞的首字母小寫,後面單詞首字母大寫

對于常量,所有字母采用大寫,多個單詞用下劃線隔開,比如WIDTH=100,BRUSH_COLOR='#00ff00'

當變量是jQuery類型時,建議以$開頭,比如var $element=$('a'); 之後就可以在後面的代碼中很友善地使用它,并且與其他變量容易區分開來

代碼混淆與壓縮

或許你很早就注意到了,你下載下傳的插件裡面,一般都會提供一個壓縮的版本一般在檔案名裡帶個'min'字樣。也就是minified的意思,壓縮濃縮後的版本。并且平時我們使用的jQuery也是官網提供的壓縮版本,jquery.min.js。這裡的壓縮不是指代碼進行功能上的壓縮,而是通過将代碼裡面的變量名,方法函數名等等用更短的名稱來替換,并且删除注釋(如果有的話)删除代碼間的空白及換行所得到的濃縮版本。同時由于代碼裡面的各種名稱都已經被替代,别人無法閱讀和厘清其邏輯,也起到了混淆代碼的作用。

壓縮的好處

  • 源碼經過混淆壓縮後,體積大大減小,使代碼變得輕量級,同時加快了下載下傳速度,兩面加載變快。比如正常jQuery v1.11.0的源碼是276kb,而壓縮後的版本僅94.1kb!體積減小一半還多。這個體積的減小對于檔案下載下傳速度的提升不可小觑。
  • 經過壓縮混淆後,代碼還能閱讀嘛?當然不能,是以順帶還起到了代碼保護的作用。當然隻是針對你編寫了一些比較酷的代碼又不想别人抄襲的情況。對于jQuery社群,這裡本身就是開源的世界,同時JavaScript這東西其實也沒什麼實質性方法可以防止别人檢視閱讀你的代碼,畢竟有混淆就有反混淆工具,這裡代碼壓縮更多的還是上面提到的壓縮檔案的作用,同時一定程度上防止别人抄襲。

插件釋出

可以将插件代碼放到GitHub上建立一個Service Hook,這樣做的目的是你以後更新的插件後,jQuery可以自動去擷取新版本的資訊然後展示在插件中心的頁面上

然後需要制作一個JSON格式的清單檔案,其中包括關于插件的基本資訊.示例如下:

{
    "name": "sliphover",
    "title": "SlipHover",
    "description": "Apply direction aware  2D/3D hover effect to images",
    "keywords": [
        "direction-aware",
        "animation",
        "effect",
        "hover",
        "image",
        "overlay",
        "gallery"
    ],
    "version": "1.1.1",
    "author": {
        "name": "Wayou",
        "email": "[email protected]",
        "url": "https://github.com/Wayou"
    },
    "maintainers": [
        {
           "name": "Wayou",
            "email": "[email protected]",
            "url": "https://github.com/Wayou"
        }
    ],
    "licenses": [
        {
            "type": "MIT",
            "url": "https://github.com/jquery/jquery-color/blob/2.1.2/MIT-LICENSE.txt"
        }
    ],
    "bugs": "https://github.com/Wayou/sliphover/issues",
    "homepage": "http://wayou.github.io/SlipHover/",
    "docs": "http://wayou.github.io/SlipHover/",
    "demo":"http://wayou.github.io/SlipHover/",
    "download": "https://github.com/Wayou/SlipHover/zipball/master",
    "dependencies": {
        "jquery": ">=1.5"
    }
}
                       

然後就可以在插件的根目錄執行現行git代碼來釋出插件了.以後每次你的插件有新版本釋出隻需更新上面指令中的版本,建立新的tag,這樣jQuery插件中心就會自動擷取到新版本資訊了