JavaScript:prototype query 插件

(http://www.javaeye.com/topic/545971)
prototype 是在 IE 4 及其以后版本引入的一个针对于某一类的对象的方法,而且特殊的地方便在于:它是一个给类的对象添加方法的方法!这一点可能听起来会有点乱,别急,下面我便通过实例对这一特殊的方法作已下讲解:

  首先,我们要先了解一下类的概念,JavaScript 本身是一种面向对象的语言,它所涉及的元素根据其属性的不同都依附于某一个特定的类。我们所常见的类包括:数组变量(Array)、逻辑变量(Boolean)、日期变量(Date)、结构变量(Function)、数值变量(Number)、对象变量(Object)、字符串变量(String) 等,而相关的类的方法,也是程序员经常用到的(在这里要区分一下类的注意和属性发方法),例如数组的push方法、日期的get系列方法、字符串的split方法等等,

  但是在实际的编程过程中不知道有没有感觉到现有方法的不足?prototype 方法应运而生!下面,将通过实例由浅入深讲解 prototype 的具体使用方法:


1、最简单的例子,了解 prototype:
(1) Number.add(num):作用,数字相加
实现方法:Number.prototype.add = function(num){return(this+num);}
试验:alert((3).add(15)) -> 显示 18


(2) Boolean.rev(): 作用,布尔变量取反
实现方法:Boolean.prototype.rev = function(){return(!this);}
试验:alert((true).rev()) -> 显示 false

是不是很简单?这一节仅仅是告诉读者又这么一种方法,这种方法是这样运用的。


2、已有方法的实现和增强,初识 prototype:
(1) Array.push(new_element)
  作用:在数组末尾加入一个新的元素
  实现方法:
  Array.prototype.push = function(new_element){
        this[this.length]=new_element;
        return this.length;
    }
  让我们进一步来增强他,让他可以一次增加多个元素!
  实现方法:
  Array.prototype.pushPro = function() {
        var currentLength = this.length;
        for (var i = 0; i < arguments.length; i++) {
            this[currentLength + i] = arguments[i];
        }
        return this.length;
    }
  应该不难看懂吧?以此类推,你可以考虑一下如何通过增强 Array.pop 来实现删除任意位置,任意多个元素(具体代码就不再细说了)

(2) String.length
  作用:这实际上是 String 类的一个属性,但是由于 JavaScript 将全角、半角均视为是一个字符,在一些实际运用中可能会造成一定的问题,现在我们通过 prototype 来弥补这部不足。
  实现方法:
  String.prototype.cnLength = function(){
        var arr=this.match(/[^/x00-/xff]/ig);
        return this.length+(arr==null?0:arr.length);
    }
  试验:alert("EaseWe空间Spaces".cnLength()) -> 显示 16
  这里用到了一些正则表达式的方法和全角字符的编码原理,由于属于另两个比较大的类别,本文不加说明,请参考相关材料。


3、新功能的实现,深入 prototype:在实际编程中所用到的肯定不只是已有方法的增强,更多的实行的功能的要求,下面我就举两个用 prototype 解决实际问题的例子:
(1) String.left()
  问题:用过 vb 的应该都知道left函数,从字符串左边取 n 个字符,但是不足是将全角、半角均视为是一个字符,造成在中英文混排的版面中不能截取等长的字符串
  作用:从字符串左边截取 n 个字符,并支持全角半角字符的区分
  实现方法:
  String.prototype.left = function(num,mode){
        if(!//d+/.test(num))return(this);
        var str = this.substr(0,num);
        if(!mode) return str;
        var n = str.Tlength() - str.length;
        num = num - parseInt(n/2);
        return this.substr(0,num);
    }
  试验:
     alert("EaseWe空间Spaces".left(8)) -> 显示 EaseWe空间
     alert("EaseWe空间Spaces".left(8,true)) -> 显示 EaseWe空
  本方法用到了上面所提到的String.Tlength()方法,自定义方法之间也能组合出一些不错的新方法呀!

(2) Date.DayDiff()
  作用:计算出两个日期型变量的间隔时间(年、月、日、周)
  实现方法:
  Date.prototype.DayDiff = function(cDate,mode){
        try{
            cDate.getYear();
        }catch(e){
            return(0);
        }
        var base =60*60*24*1000;
        var result = Math.abs(this - cDate);
        switch(mode){
            case "y":
                result/=base*365;
                break;
            case "m":
                result/=base*365/12;
                break;
            case "w":
                result/=base*7;
                break;
            default:
                result/=base;
                break;
        }
        return(Math.floor(result));
    }
  试验:alert((new Date()).DayDiff((new Date(2002,0,1)))) -> 显示 329
     alert((new Date()).DayDiff((new Date(2002,0,1)),"m")) -> 显示 10
  当然,也可以进一步扩充,得出响应的小时、分钟,甚至是秒。

(3) Number.fact()
  作用:某一数字的阶乘
  实现方法:
  Number.prototype.fact=function(){
        var num = Math.floor(this);
        if(num<0)return NaN;
        if(num==0 || num==1)
            return 1;
        else
            return (num*(num-1).fact());
    }
  试验:alert((4).fact()) -> 显示 24
  这个方法主要是说明了递归的方法在 prototype 方法中也是可行的!
jQuery插件的开发包括两种:

一种是类级别的插件开发,即给jQuery添加新的全局函数,相当于给jQuery类本身添加方法。jQuery的全局函数就是属于jQuery命名空间的函数,另一种是对象级别的插件开发,即给jQuery对象添加方法。下面就两种函数的开发做详细的说明。

1、类级别的插件开发

类级别的插件开发最直接的理解就是给jQuery类添加类方法,可以理解为添加静态方法。典型的例子就是$.AJAX()这个函数,将函数定义于jQuery的命名空间中。关于类级别的插件开发可以采用如下几种形式进行扩展:

1.1 添加一个新的全局函数

添加一个全局函数,我们只需如下定义:

 

Java代码
  1. jQuery.foo = function() {    
  2. alert('This is a test. This is only a test.');   
  3. };    
jQuery.foo = function() { 
alert('This is a test. This is only a test.');
};  

 

 

1.2 增加多个全局函数

添加多个全局函数,可采用如下定义:

Java代码
  1. jQuery.foo = function() {    
  2. alert('This is a test. This is only a test.');   
  3. };   
  4. jQuery.bar = function(param) {    
  5. alert('This function takes a parameter, which is "' + param + '".');   
  6. };    
  7. 调用时和一个函数的一样的:jQuery.foo();jQuery.bar();或者$.foo();$.bar('bar');  
jQuery.foo = function() { 
alert('This is a test. This is only a test.');
};
jQuery.bar = function(param) { 
alert('This function takes a parameter, which is "' + param + '".');
}; 
调用时和一个函数的一样的:jQuery.foo();jQuery.bar();或者$.foo();$.bar('bar');

1.3 使用jQuery.extend(object); 

Java代码
  1. jQuery.extend({       
  2. foo: function() {       
  3. alert('This is a test. This is only a test.');       
  4. },       
  5. bar: function(param) {       
  6. alert('This function takes a parameter, which is "' + param +'".');       
  7. }      
  8. });  
jQuery.extend({    
foo: function() {    
alert('This is a test. This is only a test.');    
},    
bar: function(param) {    
alert('This function takes a parameter, which is "' + param +'".');    
}   
});

 

1.4 使用命名空间

虽然在jQuery命名空间中,我们禁止使用了大量的javaScript函数名和变量名。但是仍然不可避免某些函数或变量名将于其他jQuery插件冲突,因此我们习惯将一些方法封装到另一个自定义的命名空间。

Java代码
  1. jQuery.myPlugin = {           
  2. foo:function() {           
  3. alert('This is a test. This is only a test.');           
  4. },           
  5. bar:function(param) {           
  6. alert('This function takes a parameter, which is "' + param + '".');     
  7. }          
  8. };   
  9. 采用命名空间的函数仍然是全局函数,调用时采用的方法:   
  10. $.myPlugin.foo();          
  11. $.myPlugin.bar('baz');  
jQuery.myPlugin = {        
foo:function() {        
alert('This is a test. This is only a test.');        
},        
bar:function(param) {        
alert('This function takes a parameter, which is "' + param + '".');  
}       
};
采用命名空间的函数仍然是全局函数,调用时采用的方法:
$.myPlugin.foo();       
$.myPlugin.bar('baz');

 

通过这个技巧(使用独立的插件名),我们可以避免命名空间内函数的冲突。

2、对象级别的插件开发

对象级别的插件开发需要如下的两种形式:、

形式1:  

Java代码
  1. (function($){      
  2. $.fn.extend({      
  3. pluginName:function(opt,callback){      
  4.           // Our plugin implementation code goes here.        
  5. }      
  6. })      
  7. })(jQuery);     
(function($){   
$.fn.extend({   
pluginName:function(opt,callback){   
          // Our plugin implementation code goes here.     
}   
})   
})(jQuery);   

形式2

 

Java代码
  1. (function($) {        
  2. $.fn.pluginName = function() {      
  3.      // Our plugin implementation code goes here.      
  4. };      
  5. })(jQuery);      
(function($) {     
$.fn.pluginName = function() {   
     // Our plugin implementation code goes here.   
};   
})(jQuery);    
       上面定义了一个jQuery函数,形参是$,函数定义完成之后,jQuery这个实参传递进去.立即调用执行。这样的好处是,我们在写jQuery插件时,也可以使用$这个别名,而不会与prototype引起冲突.

2.1 JQuery名称空间下申明一个名字

这是一个单一插件的脚本。如果你的脚本中包含多个插件,或者互逆的插件(例如: $.fn.doSomething() $.fn.undoSomething()),那么你需要声明多个函数名字。但是,通常当我们编写一个插件时,力求仅使用一个名字来包含它的所有内容。我们的示例插件命名为“highlight    

 

Java代码
  1. $.fn.hilight = function() {     
  2.   // Our plugin implementation code goes here.     
  3. };     
  4. 我们的插件通过这样被调用:   
  5. $('#myDiv').hilight();     
$.fn.hilight = function() {  
  // Our plugin implementation code goes here.  
};  
我们的插件通过这样被调用:
$('#myDiv').hilight();   

 

但是如果我们需要分解我们的实现代码为多个函数该怎么办?有很多原因:设计上的需要;这样做更容易或更易读的实现;而且这样更符合面向对象。 这真是一个麻烦事,把功能实现分解成多个函数而不增加多余的命名空间。出于认识到和利用函数是javascript中最基本的类对象,我们可以这样做。就像其他对象一样,函数可以被指定为属性。因此我们已经声明“hilight”jQuery的属性对象,任何其他的属性或者函数我们需要暴露出来的,都可以在"hilight" 函数中被声明属性。稍后继续。
2.2
接受options参数以控制插件的行为

让我们为我们的插件添加功能指定前景色和背景色的功能。我们也许会让选项像一个options对象传递给插件函数。例如:   

Java代码
  1. // plugin definition     
  2. $.fn.hilight = function(options) {     
  3.   var defaults = {     
  4.     foreground: 'red',     
  5.     background: 'yellow'     
  6.   };     
  7.   // Extend our default options with those provided.     
  8.   var opts = $.extend(defaults, options);     
  9.   // Our plugin implementation code goes here.     
  10. };     
  11. 我们的插件可以这样被调用:   
  12. $('#myDiv').hilight({     
  13.   foreground: 'blue'     
  14. });     
// plugin definition  
$.fn.hilight = function(options) {  
  var defaults = {  
    foreground: 'red',  
    background: 'yellow'  
  };  
  // Extend our default options with those provided.  
  var opts = $.extend(defaults, options);  
  // Our plugin implementation code goes here.  
};  
我们的插件可以这样被调用:
$('#myDiv').hilight({  
  foreground: 'blue'  
});   

 

2.3 暴露插件的默认设置

我们应该对上面代码的一种改进是暴露插件的默认设置。这对于让插件的使用者更容易用较少的代码覆盖和修改插件。接下来我们开始利用函数对象。     

 

Java代码
  1. // plugin definition     
  2. $.fn.hilight = function(options) {     
  3.   // Extend our default options with those provided.     
  4.   // Note that the first arg to extend is an empty object -     
  5.   // this is to keep from overriding our "defaults" object.     
  6.   var opts = $.extend({}, $.fn.hilight.defaults, options);     
  7.   // Our plugin implementation code goes here.     
  8. };     
  9. // plugin defaults - added as a property on our plugin function     
  10. $.fn.hilight.defaults = {     
  11.   foreground: 'red',     
  12.   background: 'yellow'     
  13. };      
  14. 现在使用者可以包含像这样的一行在他们的脚本里:   
  15. //这个只需要调用一次,且不一定要在ready块中调用   
  16. $.fn.hilight.defaults.foreground = 'blue';     
  17. 接下来我们可以像这样使用插件的方法,结果它设置蓝色的前景色:   
  18. $('#myDiv').hilight();   
// plugin definition  
$.fn.hilight = function(options) {  
  // Extend our default options with those provided.  
  // Note that the first arg to extend is an empty object -  
  // this is to keep from overriding our "defaults" object.  
  var opts = $.extend({}, $.fn.hilight.defaults, options);  
  // Our plugin implementation code goes here.  
};  
// plugin defaults - added as a property on our plugin function  
$.fn.hilight.defaults = {  
  foreground: 'red',  
  background: 'yellow'  
};   
现在使用者可以包含像这样的一行在他们的脚本里:
//这个只需要调用一次,且不一定要在ready块中调用
$.fn.hilight.defaults.foreground = 'blue';  
接下来我们可以像这样使用插件的方法,结果它设置蓝色的前景色:
$('#myDiv').hilight(); 

 

如你所见,我们允许使用者写一行代码在插件的默认前景色。而且使用者仍然在需要的时候可以有选择的覆盖这些新的默认值:

// 覆盖插件缺省的背景颜色 

$.fn.hilight.defaults.foreground = 'blue'; 

// ... 

// 使用一个新的缺省设置调用插件 

$('.hilightDiv').hilight(); 

// ... 

// 通过传递配置参数给插件方法来覆盖缺省设置 

$('#green').hilight({ 

  foreground: 'green' 

});  

2.4 适当的暴露一些函数

这段将会一步一步对前面那段代码通过有意思的方法扩展你的插件(同时让其他人扩展你的插件)。例如,我们插件的实现里面可以定义一个名叫"format"的函数来格式化高亮文本。我们的插件现在看起来像这样,默认的format方法的实现部分在hiligth函数下面。

Java代码
  1. // plugin definition     
  2. $.fn.hilight = function(options) {     
  3.   // iterate and reformat each matched element     
  4.   return this.each(function() {     
  5.     var $this = $(this);     
  6.     // ...     
  7.     var markup = $this.html();     
  8.     // call our format function     
  9.     markup = $.fn.hilight.format(markup);     
  10.     $this.html(markup);     
  11.   });     
  12. };     
  13. // define our format function     
  14. $.fn.hilight.format = function(txt) {     
  15. return '<strong>' + txt + '</strong>';     
  16. };     
// plugin definition  
$.fn.hilight = function(options) {  
  // iterate and reformat each matched element  
  return this.each(function() {  
    var $this = $(this);  
    // ...  
    var markup = $this.html();  
    // call our format function  
    markup = $.fn.hilight.format(markup);  
    $this.html(markup);  
  });  
};  
// define our format function  
$.fn.hilight.format = function(txt) {  
return '<strong>' + txt + '</strong>';  
};   
      我们很容易的支持options对象中的其他的属性通过允许一个回调函数来覆盖默认的设置。这是另外一个出色的方法来修改你的插件。这里展示的技巧是进一步有效的暴露format函数进而让他能被重新定义。通过这技巧,是其他人能够传递他们自己设置来覆盖你的插件,换句话说,这样其他人也能够为你的插件写插件。
      考虑到这个篇文章中我们建立的无用的插件,你也许想知道究竟什么时候这些会有用。一个真实的例子是Cycle插件.这个Cycle插件是一个滑动显示插件,他能支持许多内部变换作用到滚动,滑动,渐变消失等。但是实际上,没有办法定义也许会应用到滑动变化上每种类型的效果。那是这种扩展性有用的地方。 Cycle插件对使用者暴露"transitions"对象,使他们添加自己变换定义。插件中定义就像这样:

$.fn.cycle.transitions = { 

// ... 

}; 

这个技巧使其他人能定义和传递变换设置到Cycle插件。

2.5 保持私有函数的私有性

这种技巧暴露你插件一部分来被覆盖是非常强大的。但是你需要仔细思考你实现中暴露的部分。一但被暴露,你需要在头脑中保持任何对于参数或者语义的改动也许会破坏向后的兼容性。一个通理是,如果你不能肯定是否暴露特定的函数,那么你也许不需要那样做。

那么我们怎么定义更多的函数而不搅乱命名空间也不暴露实现呢?这就是闭包的功能。为了演示,我们将会添加另外一个“debug”函数到我们的插件中。这个 debug函数将为输出被选中的元素格式到firebug控制台。为了创建一个闭包,我们将包装整个插件定义在一个函数中。 

Java代码
  1.  (function($) {     
  2.   // plugin definition     
  3.   $.fn.hilight = function(options) {     
  4.     debug(this);     
  5.     // ...     
  6.   };     
  7.   // private function for debugging     
  8.   function debug($obj) {     
  9.     if (window.console && window.console.log)     
  10.       window.console.log('hilight selection count: ' + $obj.size());     
  11.   };     
  12. //  ...     
  13. })(jQuery);    
 (function($) {  
  // plugin definition  
  $.fn.hilight = function(options) {  
    debug(this);  
    // ...  
  };  
  // private function for debugging  
  function debug($obj) {  
    if (window.console && window.console.log)  
      window.console.log('hilight selection count: ' + $obj.size());  
  };  
//  ...  
})(jQuery);  

 

我们的“debug”方法不能从外部闭包进入,因此对于我们的实现是私有的。

2.6 支持Metadata插件

在你正在写的插件的基础上,添加对Metadata插件的支持能使他更强大。个人来说,我喜欢这个Metadata插件,因为它让你使用不多的"markup”覆盖插件的选项(这非常有用当创建例子时)。而且支持它非常简单。更新:注释中有一点优化建议。

Java代码 复制代码
  1. $.fn.hilight = function(options) {     
  2.   // ...     
  3.   // build main options before element iteration     
  4.   var opts = $.extend({}, $.fn.hilight.defaults, options);     
  5.   return this.each(function() {     
  6.     var $this = $(this);     
  7.     // build element specific options     
  8.     var o = $.meta ? $.extend({}, opts, $this.data()) : opts;     
  9.     //...      
$.fn.hilight = function(options) {  
  // ...  
  // build main options before element iteration  
  var opts = $.extend({}, $.fn.hilight.defaults, options);  
  return this.each(function() {  
    var $this = $(this);  
    // build element specific options  
    var o = $.meta ? $.extend({}, opts, $this.data()) : opts;  
    //...    

 这些变动行做了一些事情:它是测试Metadata插件是否被安装如果它被安装了,它能扩展我们的options对象通过抽取元数据这行作为最后一个参数添加到JQuery.extend,那么它将会覆盖任何其它选项设置。现在我们能从"markup”处驱动行为,如果我们选择了“markup”

 调用的时候可以这样写: jQuery.foo();  $.foo(); 

 

Java代码
  1. <!--  markup  -->     
  2. <div class="hilight { background: 'red', foreground: 'white' }">     
  3.   Have a nice day!     
  4. </div>     
  5. <div class="hilight { foreground: 'orange' }">     
  6.   Have a nice day!     
  7. </div>     
  8. <div class="hilight { background: 'green' }">     
  9.   Have a nice day!     
  10. </div>     
  11. 现在我们能高亮哪些div仅使用一行脚本:   
  12. $('.hilight').hilight();     
<!--  markup  -->  
<div class="hilight { background: 'red', foreground: 'white' }">  
  Have a nice day!  
</div>  
<div class="hilight { foreground: 'orange' }">  
  Have a nice day!  
</div>  
<div class="hilight { background: 'green' }">  
  Have a nice day!  
</div>  
现在我们能高亮哪些div仅使用一行脚本:
$('.hilight').hilight();   

 

2.7 整合
下面使我们的例子完成后的代码:

    

Java代码
  1. // 创建一个闭包     
  2. (function($) {     
  3.   // 插件的定义     
  4.   $.fn.hilight = function(options) {     
  5.     debug(this);     
  6.     // build main options before element iteration     
  7.     var opts = $.extend({}, $.fn.hilight.defaults, options);     
  8.     // iterate and reformat each matched element     
  9.     return this.each(function() {     
  10.       $this = $(this);     
  11.       // build element specific options     
  12.       var o = $.meta ? $.extend({}, opts, $this.data()) : opts;     
  13.       // update element styles     
  14.       $this.css({     
  15.         backgroundColor: o.background,     
  16.         color: o.foreground     
  17.       });     
  18.       var markup = $this.html();     
  19.       // call our format function     
  20.       markup = $.fn.hilight.format(markup);     
  21.       $this.html(markup);     
  22.     });     
  23.   };     
  24.   // 私有函数:debugging     
  25.   function debug($obj) {     
  26.     if (window.console && window.console.log)     
  27.       window.console.log('hilight selection count: ' + $obj.size());     
  28.   };     
  29.   // 定义暴露format函数     
  30.   $.fn.hilight.format = function(txt) {     
  31.     return '<strong>' + txt + '</strong>';     
  32.   };     
  33.   // 插件的defaults     
  34.   $.fn.hilight.defaults = {     
  35.     foreground: 'red',     
  36.     background: 'yellow'     
  37.   };     
  38. // 闭包结束     
  39. })(jQuery);     
// 创建一个闭包  
(function($) {  
  // 插件的定义  
  $.fn.hilight = function(options) {  
    debug(this);  
    // build main options before element iteration  
    var opts = $.extend({}, $.fn.hilight.defaults, options);  
    // iterate and reformat each matched element  
    return this.each(function() {  
      $this = $(this);  
      // build element specific options  
      var o = $.meta ? $.extend({}, opts, $this.data()) : opts;  
      // update element styles  
      $this.css({  
        backgroundColor: o.background,  
        color: o.foreground  
      });  
      var markup = $this.html();  
      // call our format function  
      markup = $.fn.hilight.format(markup);  
      $this.html(markup);  
    });  
  };  
  // 私有函数:debugging  
  function debug($obj) {  
    if (window.console && window.console.log)  
      window.console.log('hilight selection count: ' + $obj.size());  
  };  
  // 定义暴露format函数  
  $.fn.hilight.format = function(txt) {  
    return '<strong>' + txt + '</strong>';  
  };  
  // 插件的defaults  
  $.fn.hilight.defaults = {  
    foreground: 'red',  
    background: 'yellow'  
  };  
// 闭包结束  
})(jQuery);   

 

这段设计已经让我创建了强大符合规范的插件。我希望它能让你也能做到。

3、总结

jQuery为开发插件提拱了两个方法,分别是:

jQuery.fn.extend(object);  jQuery对象添加方法。
jQuery.extend(object);  
为扩展jQuery类本身.为类添加新的方法。

3.1 jQuery.fn.extend(object);

fn 是什么东西呢。查看jQuery代码,就不难发现。

jQuery.fn = jQuery.prototype = {  

init: function( selector, context ) {//....   

//......  

};   

原来 jQuery.fn = jQuery.prototype.prototype肯定不会陌生啦。虽然 javascript 没有明确的类的概念,但是用类来理解它,会更方便。jQuery便是一个封装得非常好的类,比如我们用 语句 $("#btn1") 会生成一个 jQuery类的实例。

jQuery.fn.extend(object); jQuery.prototype进得扩展,就是为jQuery类添加成员函数jQuery类的实例可以使用这个成员函数

比如我们要开发一个插件,做一个特殊的编辑框,当它被点击时,便alert 当前编辑框里的内容。可以这么做:

$.fn.extend({        

     alertWhileClick:function(){       

         $(this).click(function(){      

              alert($(this).val());       

          });       

      }       

});   

$("#input1").alertWhileClick(); //页面上为:<input id="input1" type="text"/>

$("#input1") 为一个jQuery实例,当它调用成员方法 alertWhileClick后,便实现了扩展,每次被点击时它会先弹出目前编辑里的内容。

3.2 jQuery.extend(object); 

jQuery类添加添加类方法,可以理解为添加静态方法。如:

$.extend({  

    add:function(a,b){return a+b;}  

});  

便为 jQuery 添加一个为 add 的 静态方法,之后便可以在引入 jQuery 的地方,使用这个方法了,$.add(3,4); //return 7

【前言】
jQuery已经被广泛使用,凭借其简洁的API,对DOM强大的操控性,易扩展性越来越受到web开发人员的喜爱,我在社区也发布了很多的jQuery插件,经常有人询问一些技巧,因此干脆写这么一篇文章给各位jQuery爱好者,算是抛砖引玉吧。

【基础】
a)样式
很多人会认为样式是个很复杂的东西,需要沉着冷静的心态加上非凡的审美观才能设计出赏心悦目的UI,抛开图片设计不说,其实css也就是那么些属性:position,margin,padding,width,height,left,top,float,border,background...

UI设计的漂亮与否在很大程度上依赖于设计人员对配色的把握和整体效果的协调。举个简单的例子,一个简单的页面,马虎的人:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> < html  xmlns ="http://www.w3.org/1999/xhtml" >
< head >
    
< title > Test Page </ title >
</ head >
< body >
    jQuery是一个框架!压缩后有30多k吧。
</ body >
</ html >

 

细心的人:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> < html  xmlns ="http://www.w3.org/1999/xhtml" >
< head >
    
< title > Test Page </ title >
    
< style  type ="text/css" >
        body
        
{
            font-family
: '宋体' ;
            font-size
: 12px ;
            
}
    
</ style >
</ head >
< body >
    jQuery是一个框架!压缩后有30多k吧。
</ body >
</ html >

专心的人:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> < html  xmlns ="http://www.w3.org/1999/xhtml" >
< head >
    
< title > Test Page </ title >
    
< style  type ="text/css" >
        body
        
{
            font-family
: 'Verdana','宋体' ;
            font-size
: 12px ;
            
}
    
</ style >
</ head >
< body >
    jQuery是一个框架!压缩后有30多k吧。
</ body >
</ html >


我们对比一下三者的UI效果:



一目了然,或许很多的站点失去关注正是因为这不起眼的font-family,font-size。当然这还只是个简单的例子,掌握css应该从简单做起,从基本入手,在实践中运用并不断深入。

b)脚本
我们同样需要对javascript有着深刻的理解,对dom, xhr, Regex, call-apply, prototype等都应该有一定的了解。

有人会说要这些有啥用啊,对dom的操作其实通过getElementById, getElementsByTagName以及其他的API都可以轻松的完成,这话是没错,当思路确定后,思想才是重点,一段代码是精华还是糟粕很容易就可以区分出来,究其原因还是取决你自己,举个简单的例子,大量的html组装,路人甲:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> var  a  =   new  Array( 10 );
var  menu  =   '' ;   
for  ( var  i  =   0 ; i  <  a.length; i ++ ) {
  menu 
+=   ' <li class="style_ '   +  a[i]  +   ' " > '   +  a[i]  +   ' </li> ' ;
}

 

路人乙:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> String.prototype.format  =   function () {
    
var  args  =  arguments;
    
return   this .replace( / {(/d{1})} / g,  function () {
        
return  args[arguments[ 1 ]];
    });
};

var  a  =   new  Array( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 );
var  m  =   ' <li class="style_{0}" >{0}</li> ' ;

for  ( var  i  =   0 ; i  <  a.length; i ++ ) {
    menu 
+=  m.format(a[i]);
}


在实现方式明确的情况下,优雅高效的代码显然更具吸引力。

【实践
jQuery开发或使用,更多的灵感是来自实践,而不是copy||paste(奉行拿来主义的同学可以离开了)。

那么在这里我会用一个简单的例子来阐述jQuery插件开发的流程,能否举一反三就看各位看官了。

【目的】

开发一个插件之前我们需要对自己的目的有一个清醒的认识,有很明确的方向感,那么此次我作为示例插件的目的,就是呈现一个用于UI的Slider - 滑动条,常年从事于或暂时专注于win32开发的同学应该比较了解。

草图

真正动手编码之前我们还需要有一个草图来描述自己插件的“长相”(事件驱动或API封装的可以忽略)。
很多的同学在做UI开发前往往会忙于搜集各种小图片(非精通ps或iconworkshop人士),其实漂亮的图标的确可以美化我们的UI,不过我一般的处理方式是编写易于扩展的css,前期的UI呈现尽量少使用图片,多用线条完成。

ok,言归正卷,那么我的slider设计草图是:

 

解释下下文将用到的几个词:
slider: 此部分是作为拖拽手柄来使用,用户可以通过拖拽此部分来更新completed bar的位置。
completed: 此部分作为bar的内嵌元素,作为特殊效果来显示slider与起始点的距离,亦即与slider的value值关联。
bar: slider的载体,completed的满值。

思路:
slider作为手柄提供拖拽功能,作用区域为bar,拖拽过程中completed条必须实时更新(长度),影响区域为slider至bar左端的距离。

【编码】

开发jQuery UI/Effect 插件在很多时候都需要与UI交互,因此在呈现上需要提供Html tree来绘制我们的插件,最终通过js dom来输出,那么在绘制简单的dom结构的时候我会直接用js来完成,不过如果嵌套比较复杂的话,我们还是应该先用html来完成,然后转变成js输出。

html tree:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> < div  class ="defaultbar" >
  
< div  class ="jquery-completed" >   </ div >
  
< div  class ="jquery-jslider" >   </ div >
</ div >

deafultbar -> bar
jquery-completed -> completed
jquery-jslider -> slider

前期UI呈现上我们不使用图片,尽量用线条、颜色来完成:

Css
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->/**//*----default skin----*/
.defaultbar
{}{
    margin-top
: 10px;
    height
: 5px;
    background-color
: #FFFFE0;
    border
: 1px solid #A9C9E2;
    position
: relative;
    
}

.defaultbar .jquery-completed
{}{
    height
: 3px;
    background-color
: #7d9edb;
    top
: 1px;
    left
:1px;
    position
: absolute;
    
}

.defaultbar .jquery-jslider
{}{
    height
: 15px;
    background-color
: #E6E6FA;
    border
: 1px solid #A5B6C8;
    top
: -6px;
    display
: block;
    cursor
: pointer;
    position
: absolute;
    
}

将bar的position属性设置成relative,以方便子节点的浮动(子节点使用position:absolute来获得内联浮动效果)。

那么我们可以看下这个css和html tree产生的UI效果:

ok,具备了所需的元素 - slider, completed, bar.

一些规范:

当我们画出了UI之后就可以正式编写jQuery插件代码了,不过在着之前我们还需要对jQuery插件开发的一些规范性有一些了解。

1. 使用闭包:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> ( function ($) {
  
//  Code goes here
})(jQuery);

这是来自jQuery官方的插件开发规范要求,使用这种编写方式有什么好处呢?

a) 避免全局依赖。

b) 避免第三方破坏。

c) 兼容jQuery操作符'$'和'jQuery '

我们知道这段代码在被解析时会形同如下代码:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> var  jq  =   function ($) {
  
//  Code goes here
};
jq(jQuery);

这样效果就一目了然了。

2. 扩展

jQuery提供了2个供用户扩展的‘基类’ - $.extend和$.fn.extend.

$.extend 用于扩展自身方法,如$.ajax, $.getJSON等,$.fn.extend则是用于扩展jQuery类,包括方法和对jQuery对象的操作。为了保持jQuery的完整性,我比较趋向于使用$.fn.extend进行插件开发而尽量少使用$.extend.

3. 选择器

jQuery提供了功能强大,并兼容多种css版本的选择器,不过发现很多同学在使用选择器时并未注重效率的问题。

a) 尽量使用Id选择器,jQuery的选择器使用的API都是基于getElementById或getElementsByTagName,因此可以知道效率最高的是Id选择器,因为jQuery会直接调用getElementById去获取dom,而通过样式选择器获取jQuery对象时往往会使用 getElementsByTagName去获取然后筛选。

b) 样式选择器应该尽量明确指定tagName, 如果开发人员使用样式选择器来获取dom,且这些dom属于同一类型,例如获取所有className为jquery的div,那么我们应该使用的写法是$('div.jquery')而不是$('.jquery'),这样写的好处非常明显,在获取dom时jQuery会获取div然后进行筛选,而不是获取所有dom再筛选。

c) 避免迭代,很多同学在使用jQuery获取指定上下文中的dom时喜欢使用迭代方式,如$('.jquery .child'),获取className为jquery的dom下的所有className为child的节点,其实这样编写代码付出的代价是非常大的,jQuery会不断的进行深层遍历来获取需要的元素,即使确实需要,我们也应该使用诸如$(selector,context), $('selector1>selector2'), $(selector1).children(selector2), $(selctor1).find(selector2)之类的方式。

开始编码

话题有点扯远,ok,在对UI有了清晰的认识后我们就可以使用js来输出html了。

我们使用jSlider来命名这个slider插件(为了避免插件冲突,插件命名时也应十分考究,这里我就俗一回)。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> $.extend($.fn, {
        
// /<summary>
         // / apply a slider UI
         // /</summary>
        jSlider:  function (setting) {
        }
});

在插件开发中比较标准的方式是将元数据独立出来并开放API,比如这里的setting参数传入值,有时候为了减少代码编写量,我习惯于直接在插件内赋值:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> var  ps  =  $.extend({
    renderTo: $(document.body),
    enable:  true ,
    initPosition:  ' max ' ,
    size: { barWidth:  200 , sliderWidth:  5  },
    barCssName:  ' defaultbar ' ,
    completedCssName:  ' jquery-completed ' ,
    sliderCssName:  ' jquery-jslider ' ,
    sliderHover:  ' jquery-jslider-hover ' ,
    onChanging:  function () { },
    onChanged:  function () { }
}, setting);

 

规范的做法:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> $.fn.jSlider. default   =  {
    renderTo: $(document.body),
    enable: 
true ,
    initPosition: 
' max ' ,
    size: { barWidth: 
200 , sliderWidth:  5  },
    barCssName: 
' defaultbar ' ,
    completedCssName: 
' jquery-completed ' ,
    sliderCssName: 
' jquery-jslider ' ,
    sliderHover: 
' jquery-jslider-hover ' ,
    onChanging: 
function () { },
    onChanged: 
function () { }
    };

$.extend({},$.fn.jSlider.
default ,setting);

 

ok, 下面描述下我所定义的这些API的作用:
renderTo: jSlider的载体、容器,可以是一个jQuery对象,也可以是选择器。
enable: jSlider插件是否可用,true时end-user可拖拽,否则禁止。
initPosition: jSlider的初始值,‘max’或者‘min’,亦即 slider的value值,1或者0。
size: jSlider的参数,包括2个值barWidth - bar的长度, sliderWidth - slider的长度。
barCssName: bar的样式名称,便于end-user自行扩展样式。
completedCssName: completed的样式名称。
sliderCssName: slider的样式名称。
sliderHover: slider聚焦时的样式名称。
onChanging: slider被拖拽时触发的事件。
onChanged: slider拖拽结束时触发的事件。

此时我们需要将renderTo强制转换成jQuery对象(兼容使用selector的情况):

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> ps.renderTo  =  ( typeof  ps.renderTo  ==   ' string '   ?
                 $(ps.renderTo) : ps.renderTo);


然后将html tree输出到render:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> /*  ---------->
html tree:
<div> ---->sliderbar
<div>&nbsp;</div>   ----> completed bar
<div>&nbsp;</div>   ----> slider                  
</div>
<-----------
*/
var  sliderbar  =  $( ' <div><div>&nbsp;</div><div>&nbsp;</div></div> ' )
                    .attr(
' class ' , ps.barCssName)
                        .css(
' width ' , ps.size.barWidth)
                            .appendTo(ps.renderTo);

var  completedbar  =  sliderbar.find( ' div:eq(0) ' )
                        .attr(
' class ' , ps.completedCssName);

var  slider  =  sliderbar.find( ' div:eq(1) ' )
                .attr(
' class ' , ps.sliderCssName)
                    .css(
' width ' , ps.size.sliderWidth);

这样我们就在UI上直接呈现了Html并且用定制的css进行渲染,分别用sliderbar, completedbar, slider对我们需要的三个对象进行缓存。

ok, 在呈现了UI后我们就需要提供方法来实现slider的拖拽,在这之前我们还需要实现一个方法,就是completedbar的实时更新,即在拖动slider的时候让completedbar始终填充左侧区域:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> var  bw  =  sliderbar.width(), sw  =  slider.width();
// make sure that the slider was displayed in the bar(make a limited)
ps.limited  =  { min:  0 , max: bw  -  sw };

if  ( typeof  window.$sliderProcess  ==   ' undefined ' ) {
    window.$sliderProcess 
=   new  Function( ' obj1 ' ' obj2 ' ' left ' ,
                                     
' obj1.css(/ ' left/ ' ,left);obj2.css(/ ' width/ ' ,left); ' );
}
$sliderProcess(slider, completedbar, eval(
' ps.limited. '   +  ps.initPosition));

bw,sw用来存储sliderbar和slider的长度,此处没有直接使用ps.size里的值是为了防止样式里的border-width对width造成破坏。

定义一个私用成员limited来存储slider[left]的最大值和最小值,并在后面直接使用eval('ps.limited.' + ps.initPosition)来获取,从而避免switch操作。

同时还需定义一个全局Function用来定位completedbar的填充长度以及slider左侧距离,我给其命名为$sliderProcess。

那么我们接下来剩下的工作就是slider的拖拽功能了,那么在这里我会用到之前发布的一款jQuery拖拽插件,并做适量的订制:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> // drag and drop
var  slide  =  {
    drag: 
function (e) {
        
var  d  =  e.data;
        
var  l  =  Math.min(Math.max(e.pageX  -  d.pageX  +  d.left, ps.limited.min), ps.limited.max);

        $sliderProcess(slider, completedbar, l);
        
// push two parameters: 1st:percentage, 2nd: event
        ps.onChanging(l  /  ps.limited.max, e);
    },
    drop: 
function (e) {
        slider.removeClass(ps.sliderHover);
        
// push two parameters: 1st:percentage, 2nd: event
        ps.onChanged(parseInt(slider.css( ' left ' ))  / ps.limited.max, e);

        $().unbind(
' mousemove ' , slide.drag).unbind( ' mouseup ' , slide.drop);
    }
};

if  (ps.enable) {
    
// bind events
    slider.bind( ' mousedown ' function (e) {
        
var  d  =  {
            left: parseInt(slider.css(
' left ' )),
            pageX: e.pageX
        };
        $(
this ).addClass(ps.sliderHover);
        $().bind(
' mousemove ' , d, slide.drag).bind( ' mouseup ' , d, slide.drop);
    });
}

这样当jSlider enable属性为true时,在end-user按下鼠标时绑定mousemove事件,在鼠标弹起时移除,我们只需要同步更新slider的left 属性和completedbar的width即可,同时在drag中绑定onChanging方法,在drop中绑定onChanged方法,向这两个方法推送的参数相同,1>百分比,即value值,介于0~1,2>event。

那么至此我们的jSlider插件就基本成型,向用户提供了一个可拖拽的slider。

【扩展】
有的时候用户却不是那么容易满足,于是有人高呼:“我要自己设置value,为什么不提供这个功能?”。

那么这时我们就需要为用户公开一个方法,用于设置jSlider的value,首先考虑的是作为方法需要一个作用对象(jSlider),那么此时我又不想将作用对象作为参数传入,那么我们还是将这个方法作为插件来开发,我们将方法命名为setSliderValue,开放2个参数,v(value值)和 callback(设置完成后的回调函数)。

即:$.fn.setSliderValue(v,callback);

ok,那么剩下的就是作用对象了,由之前的设计可知,在slider拖动时主要作用于2个对象,slider和completedbar,那么我们在jSlider插件末尾加上一段代码来返回slider对象:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> slider.data  =  { bar: sliderbar, completed: completedbar };
return  slider;

这样我们在初始化jSlider的时候就可以直接用一个变量来获取jSlider对象,然后调用setSliderValue方法了,伪码:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> var  slider  =  $.fn.jSlider({});
slider.setSliderValue(v,
function (){});

setSliderValue代码:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> try  {
    
// validate
     if  ( typeof  v  ==   ' undefined '   ||  v  <   0   ||  v  >   1 ) {
        
throw   new  Error( ' / ' v/ '  must be a Float variable between 0 and 1. ' );
    }

    
var  s  =   this ;

    
// validate 
     if  ( typeof  s  ==   ' undefined '   ||
        
typeof  s.data  ==   ' undefined '   ||
            
typeof  s.data.bar  ==   ' undefined ' ) {
        
throw   new  Error( ' You bound the method to an object that is not a slider! ' );
    }

    $sliderProcess(s, s.data.completed, v 
*  s.data.bar.width());

    
if  ( typeof  callback  !=   ' undefined ' ) { callback(v); }
}
catch  (e) {
    alert(e.message);
}

这里同样调用了全局Function $sliderProcess在设置slider的value值时进行completedbar[width]和slider[left]的更新。由于此处进行了异常处理,所以如果end-user在确保setSliderValue被作用于jSlider对象的时候可以删除此异常处理代码。

皮肤
根据jSlider的API我们可以更加方便的为其设定皮肤,为了让jSlider更加专业,我们需要2张图片:

用来作为completedbar背景的'bar'和用来作为slider背景的'slider',ok,我们更新下样式:

BlueSkin
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->/**//*----blue skin----*/
.bluebar
{}{
    margin-top
: 10px;
    height
: 4px;
    background
:#F7F7F7;
    border
:solid 1px #3e3e3e;
    position
: relative;
    
}

.bluebar .jquery-completed
{}{
    height
: 4px;
    background
:url(../images/slider/blue/bar.gif) left center no-repeat;
    top
: 0;
    left
:0;
    position
: absolute;
    
}

.bluebar .jquery-jslider
{}{
    height
: 17px;
    background
:url(../images/slider/blue/slider.gif) center 0 no-repeat;
    top
: -4px;
    display
: block;
    cursor
: pointer;
    position
: absolute;
    
}

.bluebar .jquery-jslider-hover
{}{
    background-position
:center -17px;
    
}

由于在设置样式时我仍然让子节点样式使用了API的默认值,因此在创建jSlider时我们只需要设置barCssName就行了:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> var  blue  =  $.fn.jSlider({
    renderTo: 
' #slidercontainer ' ,
    size: { barWidth: 
500 , sliderWidth:  10  },
    barCssName: 
' bluebar ' ,
    onChanging: 
function (percentage, e) {
        
//  code goes here
    }
});

呈现出来的UI:

我们这样来设置其值:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> // set percentage with a callback function
blue.setSliderValue( 0.65 function (percentage) {
    
// code goes here
});


通用性
当然,我们不仅可以将jSlider作为slider使用,有时候它也是一个progressbar:

(代码我就不贴了,直接在demo里查看 ;-) )

小结
通篇到这里就结束了,简单的介绍了一款jQuery插件的开发流程,以及开发中应该注意的细节,那么在下一篇的文章中我会向大家介绍如何打造一个通用型的 自动完成 插件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值