浅析bootstrap插件编写规范

转载.最近学习 bootstrap  转载一下留着用。

bootstrap-button.js插件是一款基于jquery的为html原生的button扩展了一些简单功能的插件,用twitter bootstrap的朋友可能再熟悉不过了,只要向button标签添加一些额外的data属性,我们就能实现点击button出现loading文字以及模拟复选和单选等功能。

下面以bootstrap-button.js的源码为实例,谈一下js插件编写的一些基本规范,笔者也是刚刚接触JS插件,权且拿这一篇,希望能抛砖引玉,欢迎讨论~

1. 源码整体结构

复制代码
1 !function ($) {

3   "use strict"; // jshint ;_;

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'

7     state = state + 'Text'
8     data.resetText || $el.data('resetText', $el[val]())

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思想和其他设计模式在插件开发过程中的体现,易维护性和可扩展性等都是我们应该考虑的因素,当然个中细节还需要我们来慢慢体会。
原创地址:http://www.cnblogs.com/fingerdancing/archive/2013/04/30/jsPlugin.html

posted @ 2015-03-17 15:40  cloudying  阅读(401)  评论(0编辑  收藏  举报