深刻理解jQuery插件開發總結(四)

jQuery插件開發模式

軟件開發過程當中是須要必定的設計模式來指導開發的,有了模式,咱們就能更好地組織咱們的代碼,而且從這些前人總結出來的模式中學到不少好的實踐。javascript

根據《jQuery高級編程》的描述,jQuery插件開發方式主要有三種:css

  1. 經過$.extend()來擴展jQuery
  2. 經過$.fn 向jQuery添加新的方法
  3. 經過$.widget()應用jQuery UI的部件工廠方式建立

一般咱們使用第二種方法來進行簡單插件開發,說簡單是相對於第三種方式。第三種方式是用來開發更高級jQuery部件的,該模式開發出來的部件帶有不少jQuery內建的特性,好比插件的狀態信息自動保存,各類關於插件的經常使用方法等,很是貼心,這裏不細說。java

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

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

運行結果: web

在這裏插入圖片描述
上面代碼中,經過 $.extend()向jQuery添加了一個sayHello函數,而後經過$直接調用。到此你能夠認爲咱們已經完成了一個簡單的jQuery插件了。

但如你所見,這種方式用來定義一些輔助方法是比較方便的。好比一個自定義的console,輸出特定格式的信息,定義一次後能夠經過jQuery在程序中任何須要的地方調用它。面試

$.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 + ' My App: ' + message);
    }
})
$.log('initializing...'); //調用
複製代碼

在這裏插入圖片描述

但這種方式沒法利用jQuery強大的選擇器帶來的便利,要處理DOM元素以及將插件更好地運用於所選擇的元素身上,仍是須要使用第二種開發方式。你所見到或使用的插件也大可能是經過此種方式開發。編程

插件開發

下面咱們就來看第二種方式的jQuery插件開發。設計模式

基本方法

先看一下它的基本格式:安全

$.fn.pluginName = function() {
    //your code goes here
}
複製代碼

基本上就是往$.fn上面添加一個方法,名字是咱們的插件名稱。而後咱們的插件代碼在這個方法裏面展開。app

好比咱們將頁面上全部連接顏色轉成紅色,則能夠這樣寫這個插件:

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

在插件名字定義的這個函數內部,this指代的是咱們在調用該插件時,用jQuery選擇器選中的元素,通常是一個jQuery類型的集合。好比$('a')返回的是頁面上全部a標籤的集合,且這個集合已是jQuery包裝類型了,也就是說,在對其進行操做的時候能夠直接調用jQuery的其餘方法而不須要再用美圓符號來包裝一下。

因此在上面插件代碼中,咱們在this身上調用jQuery的css()方法,也就至關於在調用 $('a').css()。 理解this在這個地方的含義很重要。這樣你才知道爲何能夠直接商用jQuery方法同時在其餘地方this指代不一樣時咱們又須要用jQuery從新包裝才能調用,下面會講到。初學容易被this的值整暈,但理解了就不難。

如今就能夠去頁面試試咱們的代碼了,在頁面上放幾個連接,調用插件後連接字體變成紅色。

<ul>
	<li>
		<a href="http://www.webo.com/liuwayong">個人微博</a>
	</li>
	<li>
		<a href="http://http://www.cnblogs.com/Wayou/">個人博客</a>
	</li>
	<li>
		<a href="http://wayouliu.duapp.com/">個人小站</a>
	</li>
</ul>
<p>這是p標籤不是a標籤,我不會受影響</p>
<script src="jquery-1.11.0.min.js"></script>
<script src="jquery.myplugin.js"></script>
<script type="text/javascript">
	$(function(){
		$('a').myPlugin();
	})
</script>
複製代碼

運行結果:

在這裏插入圖片描述

下面進一步,在插件代碼裏處理每一個具體的元素,而不是對一個集合進行處理,這樣咱們就能夠針對每一個元素進行相應操做。

咱們已經知道this指代jQuery選擇器返回的集合,那麼經過調用jQuery的.each()方法就能夠處理合集中的每一個元素了,但此刻要注意的是,在each方法內部,this指帶的是普通的DOM元素了,若是須要調用jQuery的方法那就須要用$來從新包裝一下。

好比如今咱們要在每一個連接顯示連接的真實地址,首先經過each遍歷全部a標籤,而後獲取href屬性的值再加到連接文本後面。 更改後咱們的插件代碼爲:

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

調用代碼仍是同樣的,咱們經過選中頁面全部的a標籤來調用這個插件 運行結果:

在這裏插入圖片描述
到此,你已經能夠編寫功能簡單的jQuery插件了。是否是也沒那麼難。 下面開始jQuery插件編寫中一個重要的部分,參數的接收。

支持鏈式調用

咱們都知道jQuery一個時常優雅的特性是支持鏈式調用,選擇好DOM元素後能夠不斷地調用其餘方法。 要讓插件不打破這種鏈式調用,只需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方法傳遞一個以上的參數時,它會將全部參數對象合併到第一個裏。同時,若是對象中有同名屬性時,合併的時候後面的會覆蓋前面的。 利用這一點,咱們能夠在插件裏定義一個保存插件參數默認值的對象,同時將接收來的參數對象合併到默認對象上,最後就實現了用戶指定了值的參數使用指定的值,未指定的參數使用插件默認值。 爲了演示方便,再指定一個參數fontSize,容許調用插件的時候設置字體大小。

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

如今,咱們調用的時候指定顏色,字體大小未指定,會運用插件裏的默認值12px。

$('a').myPlugin({
    'color': '#2C9929'
});
複製代碼

運行結果:

在這裏插入圖片描述
同時指定顏色與字體大小:

$('a').myPlugin({
    'color': '#2C9929',
    'fontSize': '20px'
});
複製代碼

在這裏插入圖片描述

保護好默認參數

注意到上面代碼調用extend時會將defaults的值改變,這樣很差,由於它做爲插件因有的一些東西應該維持原樣,另外就是若是你在後續代碼中還要使用這些默認值的話,當你再次訪問它時它已經被用戶傳進來的參數更改了。

在這裏插入圖片描述
一個好的作法是將一個新的空對象作爲$.extend的第一個參數,defaults和用戶傳遞的參數對象緊隨其後,這樣作的好處是全部值被合併到這個空對象上,保護了插件裏面的默認值。

$.fn.myPlugin = function(options) {
    var defaults = {
        'color': 'red',
        'fontSize': '12px'
    };
    var settings = $.extend({},defaults, options);//將一個空對象作爲第一個參數
    return this.css({
        'color': settings.color,
        'fontSize': settings.fontSize
    });
}
複製代碼

到此,插件能夠接收和處理參數後,就能夠編寫出更健壯而靈活的插件了。若要編寫一個複雜的插件,代碼量會很大,如何組織代碼就成了一個須要面臨的問題,沒有一個好的方式來組織這些代碼,總體感受會雜亂無章,同時也很差維護,因此將插件的全部方法屬性包裝到一個對象上,用面向對象的思惟來進行開發,無疑會使工做輕鬆不少。

面向對象的插件開發

爲何要有面向對象的思惟,由於若是不這樣,你可能須要一個方法的時候就去定義一個function,當須要另一個方法的時候,再去隨便定義一個function,一樣,須要一個變量的時候,毫無規則地定義一些散落在代碼各處的變量。

仍是老問題,不方便維護,也不夠清晰。固然,這些問題在代碼規模較小時是體現不出來的。 若是將須要的重要變量定義到對象的屬性上,函數變成對象的方法,當咱們須要的時候經過對象來獲取,一來方便管理,二來不會影響外部命名空間,由於全部這些變量名還有方法名都是在對象內部。 接着上面的例子,咱們能夠把這個插件抽象成一個美化頁面的對象,由於他的功能是設置顏色啊字體啊什麼的,固然咱們還能夠加入其餘功能好比設置下劃線啊什麼的。固然對於這個例子抽象成對象有點小題大作,這裏僅做演示用。之後我可能會介紹我編寫的一個jQuery插件SlipHover,其中代碼就比較多,這樣的模式就用得上了。 因此咱們新建一個對象命名爲Beautifier,而後咱們在插件裏使用這個對象來編碼。

//定義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();
}
複製代碼

經過上面這樣一改造,咱們的代碼變得更面向對象了,也更好維護和理解,之後要加新功能新方法,只需向對象添加新變量及方法便可,而後在插件裏實例化後便可調用新添加的東西。 插件的調用仍是同樣的,咱們對代碼的改動並不影響插件其餘地方,只是將代碼的組織結構改動了而以。

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

在這裏插入圖片描述

指定文字帶下劃線(咱們在Beautifier對象中新加的功能,默認不帶下劃線,如上面的例子)的調用:

$(function() {
    $('a').myPlugin({
        'color': '#2C9929',
        'fontSize': '20px',
        'textDecoration': 'underline'
    });
})
複製代碼

在這裏插入圖片描述

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

關於命名空間

不只僅是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!');
})();
複製代碼

原本別人的代碼也正常工做,只是最後定義的那個函數沒有用分號結尾而以,而後當頁面中引入咱們的插件時,報錯了,咱們的代碼沒法正常執行。

在這裏插入圖片描述
緣由是咱們用來充當自調用匿名函數的第一對括號與上面別人定義的函數相連,由於中間沒有分號嘛,總之咱們的代碼沒法正常解析了,因此報錯。 因此好的作法是咱們在代碼開頭加一個分號,這在任什麼時候候都是一個好的習慣。

var foo=function(){
    //別人的代碼
}//注意這裏沒有用分號結尾

//開始咱們的代碼。。。
;(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);
複製代碼

一個安全,結構良好,組織有序的插件編寫完成。

關於變量定義及命名

如今談談關於變量及方法等的命名,沒有硬性規定,但爲了規範,遵循一些約定仍是頗有必要的。

變量定義:好的作法是把將要使用的變量名用一個var關鍵字一併定義在代碼開頭,變量名間用逗號隔開。緣由有二: ● 一是便於理解,知道下面的代碼會用到哪些變量,同時代碼顯得整潔且有規律,也方便管理,變量定義與邏輯代碼分開; ● 二是由於JavaScript中全部變量及函數名會自動提高,也稱之爲JavaScript的Hoist特性,即便你將變量的定義穿插在邏輯代碼中,在代碼解析運行期間,這些變量的聲明仍是被提高到了當前做用域最頂端的,因此咱們將變量定義在一個做用域的開頭是更符合邏輯的一種作法。固然,再次說明這只是一種約定,不是必需的。

變量及函數命名 通常使用駝峯命名法(CamelCase),即首個單詞的首字母小寫,後面單詞首字母大寫,好比resultArray,requestAnimationFrame。對於常量,全部字母採用大寫,多個單詞用下劃線隔開,好比WIDTH=100,BRUSH_COLOR='#00ff00'。當變量是jQuery類型時,建議以$開頭,開始會不習慣,但常常用了以後會感受很方便,由於能夠很方便地將它與普通變量區別開來,一看到以$開頭咱們就知道它是jQuery類型能夠直接在其身上調用jQuery相關的方法,好比var $element=$('a'); 以後就能夠在後面的代碼中很方便地使用它,而且與其餘變量容易區分開來。

引號的使用:既然都扯了這些與插件主題無關的了,這裏再多說一句,通常HTML代碼裏面使用雙引號,而在JavaScript中多用單引號,好比下面代碼所示:

var name = 'Wayou';
document.getElementById(‘example’).innerHTML = '< a href="http: //wayouliu.duapp.com/">'+name+'</a>'; //href=".." HTML中保持雙引號,JavaScript中保持單引號
複製代碼

一方面,HTML代碼中原本就使用的是雙引號,另外一方面,在JavaScript中引號中還須要引號的時候,要求咱們單雙引號間隔着寫纔是合法的語句,除非你使用轉意符那也是能夠的。再者,堅持這樣的統一能夠保持代碼風格的一致,不會出現這裏字符串用雙引號包着,另外的地方就在用單引號。

代碼混淆與壓縮

進行完上面的步驟,已經小有所成了。或許你很早就注意到了,你下載的插件裏面,通常都會提供一個壓縮的版本通常在文件名裏帶個'min'字樣。也就是minified的意思,壓縮濃縮後的版本。而且平時咱們使用的jQuery也是官網提供的壓縮版本,jquery.min.js。

這裏的壓縮不是指代碼進行功能上的壓縮,而是經過將代碼裏面的變量名,方法函數名等等用更短的名稱來替換,而且刪除註釋(若是有的話)刪除代碼間的空白及換行所獲得的濃縮版本。同時因爲代碼裏面的各類名稱都已經被替代,別人沒法閱讀和分清其邏輯,也起到了混淆代碼的做用。

壓縮的好處 ● 源碼通過混淆壓縮後,體積大大減少,使代碼變得輕量級,同時加快了下載速度,兩面加載變快。好比正常jQuery v1.11.0的源碼是276kb,而壓縮後的版本僅94.1kb!體積減少一半還多。這個體積的減少對於文件下載速度的提高不可小覷。 ● 通過壓縮混淆後,代碼還能閱讀嘛?固然不能,因此順帶還起到了代碼保護的做用。固然只是針對你編寫了一些比較酷的代碼又不想別人抄襲的狀況。對於jQuery社區,這裏自己就是開源的世界,同時JavaScript這東西其實也沒什麼實質性方法能夠防止別人查看閱讀你的代碼,畢竟有混淆就有反混淆工具,這裏代碼壓縮更多的仍是上面提到的壓縮文件的做用,同時必定程度上防止別人抄襲。

工具

所使用的工具推崇的是Google開發的Closure Compiler。該工具須要Java環境的支持,因此使用前你可能須要先在機子上裝JRE, 而後再獲取Closure進行使用。 同時也有很朋在線的代碼混淆壓縮工具,用起來也很方便。這些工具都是一搜一大把的。

相關文章
相關標籤/搜索