bootstrap-button.js插件是一款基於jquery的為html原生的button擴展了一些簡單功能的插件,用twitter bootstrap的朋友可能再熟悉不過了,只要向button標簽添加一些額外的data屬性,我們就能實現點擊button出現loading文字以及模擬復選和單選等功能。
下面以bootstrap-button.js的源碼為實例,談一下js插件編寫的一些基本規范,筆者也是剛剛接觸JS插件,權且拿這一篇,希望能拋磚引玉,歡迎討論~
1. 源碼整體結構
1 !function ($) { 2 3 "use strict"; // jshint ;_; 4 5 /* BUTTON PUBLIC CLASS DEFINITION 6 * ============================== */ 7 var Button = function (element, options) {/*some code*/} 8 Button.prototype.setState = function (state) {/*some code*/} 9 Button.prototype.toggle = function () {/*some code*/} 10 11 /* BUTTON PLUGIN DEFINITION 12 * ======================== */ 13 var old = $.fn.button 14 $.fn.button = function (option) {return this.each(function () {/*some code*/})} 15 $.fn.button.defaults = {loadingText: 'loading...'} 16 $.fn.button.Constructor = Button 17 18 /* BUTTON NO CONFLICT 19 * ================== */ 20 $.fn.button.noConflict = function () {$.fn.button = old;return this;} 21 22 /* BUTTON DATA-API 23 * =============== */ 24 $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {/*some code*/}) 25 26 }(window.jQuery);
1.1. 定義一個匿名函數,並將jQuery做為函數參數傳遞進來執行
這樣我們就可以在閉包中定義自己的私有函數而不破壞全局的命名空間,而把javascript插件寫在一個相對封閉的空間,並開放可以增加擴展的地方,將不可以修改的地方定義成私有成員屬性或方法,以遵循“開閉原則”
1 !function($){ 2 //some code 3 }(window.jQuery)
其中,!function(){}()也是匿名函數一種寫法,和(function(){})()的寫法區別不大,類似的還有+function(){}(), -function(){}(), ~function(){}()等等,只是返回值不同而已
1.2. 匿名函數內的代碼構成
PUBLIC CLASS DEFINITION:類定義,定義了插件構造方法類及方法。
PLUGIN DEFINITION:插件定義,上面只是定義了插件的類,這里才是實現插件的地方。
PLUGIN NOCONFLICT:插件命名沖突解決
DATA-API:DATA-屬性接口
2. PUBLIC CLASS DEFINITION:插件類定義
2.1. 構造方法:
1 var Button = function (element, options) { 2 this.$element = $(element) 3 this.options = $.extend({}, $.fn.button.defaults, options) 4 }
這里是JavaScript中OOP思想的體現,定義一個類的構造方法再定義類的方法(屬性),這樣new出來的對象(類的具體實現)就可以調用類的公共方法和訪問類的公共屬性了,這里,在Button函數體內部定義的屬性和方法可以看做是類的私有屬性和方法,為Button.prototype對象定義的屬性和方法都可以看做是類的公共屬性和方法。這個類封裝了插件對象初始化所需的方法和屬性。
這樣,通過例如var btn = new Button(element, options);我們就定義了一個Button類型的btn對象,這里的this就是btn對象本身
Button(element, options)方法接受兩個參數:element和options
element就是與插件相關聯的DOM元素,通過
this.$element = $(element)
將element封裝成為一個jQuery對象$element,並由this(btn)對象的$element屬性引用
options是插件的一些設置選項,這里簡單說一下$.extend(target [, object1] [, objectN]),作用是將object1,...objectN對象合並到target對象中,這是一個在編寫jQuery插件過程中經常用到的方法,通過
this.options = $.extend({}, $.fn.button.defaults, options)
就實現了將用戶自定義的options覆蓋了插件的默認options: $.fn.button.defaults,並合並到一個空的對象{}中,並由this(btn)對象的options屬性引用
通過構造方法,btn的方法setState,toggle就可以調用btn的$element和options屬性了
2.2. 類的方法定義:
2.2.1. setState方法:
1 Button.prototype.setState = function (state) { 2 var d = 'disabled' 3 , $el = this.$element 4 , data = $el.data() 5 , val = $el.is('input') ? 'val' : 'html' 6 7 state = state + 'Text' 8 data.resetText || $el.data('resetText', $el[val]()) 9 10 $el[val](data[state] || this.options[state]) 11 12 // push to event loop to allow forms to submit 13 setTimeout(function () { 14 state == 'loadingText' ? 15 $el.addClass(d).attr(d, d) : 16 $el.removeClass(d).removeAttr(d) 17 }, 0) 18 }
setState(state)方法的作用是為$element添加'loading...'(loading...是$.fn.button.defaults屬性loadingText默認設置,詳見3)
這里簡單說幾點:
val = $el.is('input') ? 'val' : 'html'
是為了兼容<button>Submit</button>和<input type="button" value="submit">的兩種寫法
data.resetText || $el.data('resetText', $el[val]())
這是一個小技巧,||是短路或,意即||左邊的表達式為true則不執行||右邊的表達式,為false則執行||右邊的表達式,等價於
if(!data.resetText){ $el.data('resetText', $el[val]()); }
2.2.2. toggle方法:
1 Button.prototype.toggle = function () { 2 var $parent = this.$element.closest('[data-toggle="buttons-radio"]') 3 $parent && $parent.find('.active').removeClass('active') 4 this.$element.toggleClass('active') 5 }
toggle()方法的作用是通過為button添加'active'的class來添加“已選中”的CSS樣式
這里面
$parent && $parent.find('.active').removeClass('active')
和前面短路或的例子相似,&&是短路與,意即&&左邊的表達式為false則不執行&&右邊的表達式,為true則執行&&右邊的表達式,等價於
if($parent){ $parent.find('.active').removeClass('active'); }
2.2.3 小結:
定義了插件的類之后,下一步我們要做的是如何快捷的去調用插件,因此我們需要將插件整合到$.fn中,即jQuery對象的原型鏈中,以便jQuery對象快捷的調用插件
3. PLUGIN DEFINITION:插件定義
3.1 插件的jQuery對象級定義
1 $.fn.button = function (option) { 2 return this.each(function () { 3 var $this = $(this) 4 , data = $this.data('button') 5 , options = typeof option == 'object' && option 6 if (!data) $this.data('button', (data = new Button(this, options))) 7 if (option == 'toggle') data.toggle() 8 else if (option) data.setState(option) 9 }) 10 }
首先,$.fn.button=function(){}是在$.fn對象(插件的命名空間)下添加了button屬性,這樣我們以后就可以通過$(selector).button()來調用插件了,很簡單吧!這里擴展一下,為什么在$.fn中添加方法,$(selector)就能直接調用該方法了呢?之前閱讀jQuery的源碼,發現了這樣的架構
1 var jQuery = function( selector, context ) { 2 // The jQuery object is actually just the init constructor 'enhanced' 3 return new jQuery.fn.init( selector, context, rootjQuery ); 4 }, 5 //some code 6 jQuery.fn = jQuery.prototype = {/*some code*/} 7 jQuery.fn.init.prototype = jQuery.fn;
每次我們寫$(selector)實際上就是調用了jQuery(selector)函數一次($是jQuery的別名),都會返回一個jQuery.fn.init類型的對象(每寫一次$(selector)都會生成一個不同的jQuery對象),jQuery實際上是一個類(構造方法),jQuery.fn.init也是一個類(構造方法),jQuery.fn正是jQuery.prototype,jQuery.fn.init.prototype也正是jQuery.fn,所以添加到jQuery.fn的方法相當於被添加到了jQuery.fn.init類下面,$(selector)實質上是一個new的jQuery.fn.init類型的對象,理所當然的也就可以調用jQuery.fn.init.prototype下的方法了,也就是jQuery.fn下的方法了。看起來似乎很繞,感覺明明一個new就可以實現的對象為什么繞了這么大個圈子?其實一點都不繞,這里繞了這么多,所以jQuery才能自豪的聲稱“write less, do more”, 個中奧秘還須慢慢體會。
好像扯的有點遠了,還是回歸正題,下面這句也是需要注意的地方
return this.each(function () { var $this = $(this) //some code })
通過jQuery.each方法遍歷$(selector)的所有DOM元素,然后再通過$(this)將每個遍歷到的DOM元素封裝為單一的jQuery對象,其作用在於:對於$(selector)得到的結果集,通過形如$(selector).attr('class')方法得到的是單個結果(第一個匹配的DOM元素的class屬性),而不是一組結果,通過$(selector).attr('class','active')更會將全部的class設置為active!所以需要將$(selector)的結果集逐一封裝成$對象再去get或者set屬性,這樣才是嚴謹的做法。
if (!data) $this.data('button', (data = new Button(this, options)))
這里是真正用到data = new Button(this, options);的地方,整個$.fn.button做的最主要的事情就是將每個匹配的DOM元素的data-button屬性引用new Button(this, options)對象,其次通過判斷option來調用toggle方法還是setState方法,至此,插件才算是基本定義完了。
3.2 插件的默認設置定義:
1 $.fn.button.defaults = { 2 loadingText: 'loading...' 3 }
將插件的默認設置做為了$.fn.button的defaults屬性,這樣做帶來的好處就是給用戶修改插件的一些默認設置提供了通道,我們只需設置$.fn.button.defaults = {/*some code*/}就改變了插件的默認配置。也就是說,插件對擴展是開放的。
3.3 插件的構造器:
1 $.fn.button.Constructor = Button
開放了插件的構造方法類做為$.fn.button的Constructor屬性,使得用戶可以讀取插件的構造方法類。
4. NO CONFLICT插件名稱沖突解決
1 $.fn.button.noConflict = function () { 2 $.fn.button = old 3 return this 4 }
用法同$.noConflict,釋放$.fn.button的控制權,並重新為$.fn.button聲明一個名稱,旨在解決插件名稱和其他插件有沖突的情況
5. DATA-API DATA-屬性接口
1 $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { 2 var $btn = $(e.target) 3 if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 4 $btn.button('toggle') 5 })
此方法,向所有帶有data-toggle以button開頭的元素綁定了click事件(注意這里用了事件委托的寫法)
<button data-toggle="button">Click Me</button>
好處就是不用再寫$(selector).button(options)來初始化插件了,只要頁面加載,插件就自動完成初始化了。甚至options的某些屬性都可以寫在data-屬性中,但是$.fn.button.defaults設置的默認屬性可能會無效
6.總結
以上就是bootstrap-button.js插件的源碼分析,bootstrap-button.js也基本上是bootstrap中最簡單的插件了,但是麻雀雖小五臟俱全,bootstrap插件的編寫規范也很值得我們來學習,尤其是OOP思想和其他設計模式在插件開發過程中的體現,易維護性和可擴展性等都是我們應該考慮的因素,當然個中細節還需要我們來慢慢體會。
附:bootstrap-button使用方法http://twitter.github.io/bootstrap/javascript.html#buttons