/*摘抄自: http://www.w3cfuns.com/notes/19462/ec18ab496b4c992c437977575b12736c
前端網: 從零開始學JQUERY插件 */
jQuery 最成功的地方,是它的可擴展性,通過吸引了衆多開發者不斷爲添加功能,從而建立起了一個生態系統。
jQuery插件開發方式主要有三種:
- 通過$.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在程序中任何需要的地方調用它。
$.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上面添加一個方法,pluginName是我們的插件名稱。例如我們將頁面上所有鏈接顏色轉成紅色,則可以這樣寫這個插件:
$.fn.myPlugin = function() {
//在這裏面,this指的是用jQuery選中的元素
//example :$('a'),則this=$('a')
this.css('color', 'red');
}
所以在上面插例子中,this調用jQuery的css()方法就相當於調用 $('a').css()。
理解this很重要。這樣你才知道爲什麼可以直接用jQuery方法同時在其他地方this指代不同時我們又需要用jQuery重新包裝才能調用。
現在就可以去頁面試試我們的代碼了,在頁面上放幾個鏈接,調用插件後鏈接字體變成紅色。
<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指代的不再是一個jQuery對象,而是普通的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身上,而當用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的值改變,如果在後續代碼中還要使用defaults的話,會發現它已經被用戶傳進來的參數更改了。但是對於插件來說,defaults的值不應該被改變。
所以,將一個新的空對象做爲$.extend的第一個參數,defaults和用戶傳遞的參數對象緊隨其後,這樣所有值被合併到這個空對象上,保護了插件裏面的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,毫無規則地定義一些散落在代碼各處的變量和方法,結構不夠清晰,維護起來也不方便。
假設使用面向對象的思想設計插件,將需要的重要變量定義到對象的屬性上,函數變成對象的方法,一來方便管理,二來不會影響外部命名空間,因爲所有這些變量名還有方法名都是在對象內部。
接着上面的例子,他的功能是設置顏色啊字體啊什麼的,我們可以把這個插件抽象成一個美化頁面的對象,我們還可以加入其他功能,比如設置下劃線什麼的。
所以我們新建一個對象命名爲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'
});
})
到這裏,你可以更好地編寫複雜的插件同時很好地組織代碼了。當我們回頭去看上面的代碼時,其實也還是有改進空間的。也就是下面介紹的關於命名空間及變量各什麼的,一些雜項。
命名空間
我們在寫任何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($,window,document,undefined){
//我們的代碼。。
//blah blah blah...
})(jQuery,window,document);
而至於這個undefined,就非常有意思了,爲了得到沒有被修改的undefined,我們並沒有傳遞這個參數,但卻在接收時接收了它,因爲實際並沒有傳,所以‘undefined’那個位置接收到的就是真實的'undefined'了。這是值得細細體會的技術和思維方式,當然不是我發明的,都是從前人的經驗中學習到的。所以最後我們的插件成了這樣:
;(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);
一個安全,結構良好,組織有序的插件編寫完成。