jQuery 自定义插件

时间:2021-01-01 20:39:48


目的开发基于
jQuery的组件,类似官网的jQuery UI

 

A、准备知识

A.1(function(){})();

我们发现很多javascript代码中,都有类似的代码。

如,jquery中是(function(window, undefined ) {…})(window);

如,jqueryUI中是(function( $, undefined ) {…})(jQuery);

该段代码的实质是,先声明一个匿名函数function(arg0){…},声明完成直接调用该函数。其中arg0是个刑参,用在匿名函数内部调用。由于操作符的优先级,函数本身也需要用括号 (function(arg0){}) 。这时我们就已经完成一个匿名函数的定义。在要调用这个匿名函数,就需要给上实参(function(arg0){…})(arg1); 

举例说明:

如:

(function(str){

         alert(str);

})("output");

相当于:

function OutPutFun(str){

         alert(str);

};

OutPutFun("output");

 

A.2jQuery.extend()jQuery.fn.extend()

jQuery.extend( target [, object1 ] [, objectN ] )

Merge the contents of two or more objects together into the first object.

把两个或多个对象的内容合并到第一个对象中。

jQuery.fn.extend( object )

Extends the jQuery element set to provide new methods (used to make a typical jQuery plugin).

为扩展的jquery元素设置对外提供的新方法(用于制作标准的jquery插件)。

 

A.3javascript中的apply()call()

首先我们要明白function函数是一个typeof类型为function的对象。而call()apply()都属于Function.prototype的一个方法。所以每个function对象都有callapply方法。这两个方法的作用就是执行该函数。区别在于传递参数的方式不同。

function print(a, b, c, d){

         alert(a + b + c + d);

};

function example(a, b , c , d){

         //call方式借用print,参数显式打散传递

         print.call(this, a, b, c, d);

         //apply方式借用print, 参数作为一个数组传递,

         //这里直接用JavaScript方法内本身有的arguments数组

         print.apply(this, arguments);

         //或者封装成数组

         print.apply(this, [a, b, c, d]);

};

example('a' , 'b' , 'c', 'd');

 

A.4Array.prototype.slice.call( arguments, 1 )

首先我们要知道arguments是函数的内置变量,就是参数组成的数组。Array是内置的数组类,而slice是类的内置方法。(和javastatic静态方法有点像)。内置的类型可以通过prototype找到内置的属性方法。Array.prototype.slice (start, [end])就是其内置方法。Array.prototype.slice.call( arguments, 1 )的方法返回把参数数组中去除第一个参数后组成的新数组。

 

BjQuery plugin

B.1Getting Started入门

To write a jQuery plugin, start by adding a new function property to the jQuery.fn object where the name of the property is the name of your plugin:

jQuery插件,从给jQuery.fn对象增加一个新的功能属性开始,jQuery.fn对象属性的名字就是你的插件的名字。

jQuery.fn.myPlugin = function() {

};

But wait! Where's my awesome dollar sign that I know and love? It's still there, however to make sure that your plugin doesn't collide with other libraries that might use the dollar sign, it's a best practice to pass jQuery to an IIFE (Immediately Invoked Function Expression) that maps it to the dollar sign so it can't be overwritten by another library in the scope of its execution.

为了防止你的插件和其它使用$的库冲突,最好的办法就是通过直接调用函数表达式把jQuery传递给$

(function( $ ) {

  $.fn.myPlugin = function() {

  };

})( jQuery );

 

B.2Context上下文

Now that we have our shell we can start writing our actual plugin code. But before we do that, I'd like to say a word about context. In the immediate scope of the plugin function, the this keyword refers to the jQuery object the plugin was invoked on. This is a common slip up due to the fact that in other instances where jQuery accepts a callback, the this keyword refers to the native DOM element. This often leads to developers unnecessarily wrapping the this keyword (again) in the jQuery function.

this关键词指的是jQuery对象,就是被调用的插件。所以没必要使用$(this)来表示。

(function( $ ) {

  $.fn.myPlugin = function() {

           this.css("background-color","#F00");

  };

})( jQuery );

 

B.3return返回值

(function( $ ){

  $.fn.maxHeight = function() {

    var max = 0;

    this.each(function() {

      max = Math.max( max, $(this).height() );

    });

    return max;

  };

})( jQuery );

这个插件是返回所有jQuery对象中,最高的高度。返回最高高度的值。

var tallest = $('div').maxHeight();

 

B.4Maintaining Chainability

The previous example returns an integer value of the tallest div on the page, but often times the intent of a plugin is simply modify the collection of elements in some way, and pass them along to the next method in the chain. This is the beauty of jQuery's design and is one of the reasons jQuery is so popular. So to maintain chainability in a plugin, you must make sure your plugin returns the this keyword.

很多时候,一个插件的目的是修改一组元素。并把修改后的jQuery对象返回,交由调用者处理。

(function( $ ) {

  $.fn.myPlugin = function() {

           this.css("background-color","#F00");

return this;

  };

})( jQuery );

这个插件是修改背景色,并返回jQuery对象。

$('#abc').myPlugin().css("background-color","#0F0");

若没有return this;再修改背景色就不能了。

 

B.5Defaults and Options选项

For more complex and customizable plugins that provide many options, it's a best practice to have default settings that can get extended (using $.extend) when the plugin is invoked. So instead of calling a plugin with a large number of arguments, you can call it with one argument which is an object literal of the settings you would like to override. Here's how you do it.

         复杂的可自定义的插件一般会提供很多选项,最好的实践是在调用插件时用$.extend用自定义选项option来合并默认选项default。你可使用一个javascript对象的参数来代替很多参数。

         (function( $ ){

  $.fn.tooltip = function( options ) { 

    // Create some defaults, extending them with any options that were provided

    var settings = $.extend( {

      'location'         : 'top',

      'background-color' : 'blue'

    }, options);

    return this.each(function() {       

      // Tooltip plugin code here

    });

  };

})( jQuery );

 

B.5Namespacing命名空间

Properly namespacing your plugin is a very important part of plugin development. Namespacing correctly assures that your plugin will have a very low chance of being overwritten by other plugins or code living on the same page. Namespacing also makes your life easier as a plugin developer because it helps you keep better track of your methods, events and data.

命名空间很重要,好的命名空间和防止你的插件和其他插件的冲突的可能。

 

B.6Plugin Methods插件方法

Under no circumstance should a single plugin ever claim more than one namespace in the jQuery.fn object.

         一个插件可能会在jQuery.fn对象中声明多个命名空间。

This is a discouraged because it clutters up the $.fn namespace. To remedy this, you should collect all of your plugin's methods in an object literal and call them by passing the string name of the method to the plugin.

         不过这会导致$.fn命名空间杂乱无章。解决这个问题的办法就是使用插件方法。

(function( $ ){

         //定义方法集的对象

         var methods = {

                   init : function( options ) {

                   },

                   show : function( ) {

                   },

                   hide : function( ) {

                   },

                   update : function( content ) {

                   }

         };

         $.fn.tooltip = function( method ) {

                   //参数是字符串,并且是methods对象中对应的一个方法的命名空间

                   if ( methods[method] ) {

                            //调用第一个参数对于的函数,并把后面的参数提交给该函数

                            return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));

                   }

                   //参数是对象或为空

                   else if ( typeof method === 'object' || ! method ) {

                            //就执行methods对象中,init属性所对应的函数对象,

//并把参数method传递给该函数

                            return methods.init.apply( this, arguments );

                   }

//找不到对于的方法

else {

                            $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );

                   }

         };

})( jQuery );

 

// 第一个参数空,调用init对于的函数

$('div').tooltip();

// 第一个参数为对象,调用init对于的函数,并把参数传递给该函数

$('div').tooltip({

  foo : 'bar'

});

//第一个参数为字符串,并在方法列表中找到,就调用对于的函数

$('div').tooltip('hide');

//第一个参数为字符串,就调用对于的函数,并把后续参数传递给该函数

$('div').tooltip('update', 'This is the new tooltip content!');

 

B.6Plugin Events插件事件

A lesser known feature of the bind method is that is allows for namespacing of bound events. If your plugin binds an event, its a good practice to namespace it. This way, if you need to unbind it later, you can do so without interfering with other events that might have been bound to the same type of event. You can namespace your events by appending “.” to the type of event you're binding.

bind方法的一个鲜为人知的功能就是为命名空间绑定事件。若你的插件要绑定一个事件,一种好的解决方法就是命名这个事件。这样一来,若你要解除绑定的这个事件,你就可以直接解除它,而不会干扰可能已经绑定的同类型的其它事件。你可以通过添加.namespace来命名你要绑定的事件。

(function( $ ){

  var methods = {

     init : function( options ) {

       return this.each(function(){

         $(window).bind('resize.tooltip', methods.reposition);

       });

     },

     destroy : function( ) {

       return this.each(function(){

         $(window).unbind('.tooltip');

       })

     },

     reposition : function( ) {

     },

     show : function( ) {

     },

     hide : function( ) {

     },

     update : function( content ) {

     }

  };

  $.fn.tooltip = function( method ) {

    if ( methods[method] ) {

      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));

    } else if ( typeof method === 'object' || ! method ) {

      return methods.init.apply( this, arguments );

    } else {

      $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );

    }

  };

})( jQuery );

 

B.7Plugin Data插件数据

Often times in plugin development, you may need to maintain state or check if your plugin has already been initialized on a given element. Using jQuery's data method is a great way to keep track of variables on a per element basis. However, rather than keeping track of a bunch of separate data calls with different names, it's best to use a single object literal to house all of your variables, and access that object by a single data namespace.

在插件开发中,你可能需要维持状态或检查你的插件是否初始化完成。

(function( $ ){

  var methods = {

     init : function( options ) {

       return this.each(function(){

         var $this = $(this),

             data = $this.data('tooltip'),

             tooltip = $('

', {

               text : $this.attr('title')

             });

         // If the plugin hasn't been initialized yet

         if ( ! data ) {

          

 

           $(this).data('tooltip', {

               target : $this,

               tooltip : tooltip

           });

         }

       });

     },

     destroy : function( ) {

       return this.each(function(){

         var $this = $(this),

             data = $this.data('tooltip');

         // Namespacing FTW

         $(window).unbind('.tooltip');

         data.tooltip.remove();

         $this.removeData('tooltip');

       })

     },

     reposition : function( ) { // ... },

     show : function( ) { // ... },

     hide : function( ) { // ... },

     update : function( content ) { // ...}

  };

  $.fn.tooltip = function( method ) {

    if ( methods[method] ) {

      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));

    } else if ( typeof method === 'object' || ! method ) {

      return methods.init.apply( this, arguments );

    } else {

      $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );

    }   

  };

})( jQuery );

 

B.8Summary and Best Practices总结和最佳实践

Always wrap your plugin in a closure: (function( $ ){ })( jQuery );

用以下形式包裹你的插件。(function( $ ){})( jQuery );

Don't redundantly wrap the this keyword in the immediate scope of your plugin's function

在插件直接范围内使用this

Unless you're returning an intrinsic value from your plugin, always have your plugin's function return the this keyword to maintain chainability.

除非有特殊需求,否则就return this;

Rather than requiring a lengthy amount of arguments, pass your plugin settings in an object literal that can be extended over the plugin's defaults.

用对象来代替大量参数。

Don't clutter the jQuery.fn object with more than one namespace per plugin.

不要为一个插件注入一个以上的jQuery.fn命名空间。

Always namespace your methods, events and data.

为方法,事件和数据使用命名空间。