jQuery 插件编程指南(Plugins/Authoring)

如果你感觉 jQuery 还不错,也想用它来做点儿什么的时候,这篇文章是个很好的开始。通过插件或者方法来扩展 jQuery 可以做很多事情,通过将你的一些天才的想法做成插件,能够节省很多的开发时间。本文列出了一些基础而又最佳的实践方法和一些需要注意的常见错误来帮助你快速开始 jQuery 插件的创作。

1. 入门知识 Getting Started

开发一个 jQuery 的插件,可以从添加一个新的函数属性到 jQuery.fn 对象来开始,这个属性的名称就是你创建的插件的名字了:

jQuery.fn.myPlugin = function() {

  // 这里是你的插件代码

};

但是……那个熟悉的酷酷的 $ 符号呢?别担心,还在。为了保证你的插件不与别的库冲突就可以使用 $ 符号。通过将 jQuery 传递给一个将其映射到 $ 符号的 IIFE(Immediately Invoked Function Expression,即时调用的函数表达式)以保证它不被别的在其执行范围内的库覆盖,这在实践中是一个非常好的方法。

(function( $ ) {
  $.fn.myPlugin = function() {

    // 这里是你的插件代码

  };
})( jQuery );

这样就好多了。现在,在这个封闭的函数声明范围内,我们可以随心所欲地使用 $ 符号来替代 jQuery。

2. 前后关系(上下文) Context

现在我们就可以在这个脚本中开始写属于自己的插件代码了。在开始之前,我们先来了解一下前后关系(上下文)问题。在插件函数的当前范围内,关键词 this 指代调用该插件的 jQuery 对象。这是一个很常见的指代方法,而实际上在别的 jQuery 接受回调的实例中,关键词 this则指代其所在实例的 DOM 元素。开发者常常多此一举地在 jQuery 函数中重新封装关键词 this

(function( $ ){

  $.fn.myPlugin = function() {

    // there's no need to do $(this) because
    // "this" is already a jquery object

    // $(this) would be the same as $($('#element'));

    this.fadeIn('normal', function(){

      // the this keyword is a DOM element

    });

  };
})( jQuery );
$('#element').myPlugin();

3. 基本方法 The Basics

现在我们开始写一个能实际做点什么的插件吧。

(function( $ ){

  $.fn.maxHeight = function() {

    var max = 0;

    this.each(function() {
      max = Math.max( max, $(this).height() );
    });

    return max;
  };
})( jQuery );
var tallest = $('div').maxHeight(); // 返回最高一个 div 的高度值

这是一个简单的插件,利用 .height() 来返回页面中最高一个 div 的高度。

4. 维持可链通性 Maintaining Chainability

前面的代码返回一个整数数值,最高的 div 的高度。但是更多的时候,插件只是使用类似的方法来修改一系列元素,并将它们返回给链路中的下一个方法。这就是 jQuery 设计的优美之处,也是 jQuery 如此流行的原因之一。为了维持插件中代码的可连接性,我们需要确保插件返回关键词 this

(function( $ ){

  $.fn.lockDimensions = function( type ) {  

    return this.each(function() {

      var $this = $(this);

      if ( !type || type == 'width' ) {
        $this.width( $this.width() );
      }

      if ( !type || type == 'height' ) {
        $this.height( $this.height() );
      }

    });

  };
})( jQuery );
$('div').lockDimensions('width').css('color', 'red');

因为插件在它的当前范围中返回了关键词 this,它就维持了可链通性,然后 jQuery 集可以继续由其它 jQuery 方法来操作,例如 .css。所以如果你的插件不返回一个本身固有的值,你就应该让它返回一个插件函数当前范围的关键词 this。同样,正如你之所想,你传递到插件调用中的参数也会同时传递给当前范围的插件函数。这样一来,在前面例子中,字符串 ‘width’ 就成为插件函数的一个 type 参数。

5. 默认值和选项 Defaults and Options

很多复杂的可定制化的插件提供了很多的选项,最佳的实践就是定义一些默认的设置,这些设置又可以在调用该插件时进行扩展(使用 $.extend)。这样我们调用插件的时候就不需要包含一大串参数了,而只需要包含你希望覆盖的设置的参数就可以了。如下例所示。

(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 );
$('div').tooltip({
  'location' : 'left' // 调用 tooltip 时使用包含这一个参数来覆盖原来的 'top',其它的保持默认设置
});

这个例子中,在调用一个设定了一些默认设置的插件 tooltip 的时候,默认设置中的 location 被覆盖为 'left',而默认的 background-color 保持不变,还是'blue'。所以最后的设置对象是:

{
  'location'         : 'left',
  'background-color' : 'blue'
}

这个是一个非常赞的方式,提供一个高度可定制化的插件而不需要开发人员定义所有可用选项。

6. 命名空间 Namespacing

设定适当的命名空间是开发插件的一个非常重要的部分。正确的命名空间能够降低你的代码被当前页面上的其他插件或者代码覆盖重写的几率。作为一个插件开发人员,命名空间还能够使你的生活更轻松,因为它能够帮助你更好的跟踪你的方法、事件和数据等。

6.1 插件方法 Plugin Methods

不管在什么情况下,一个单独的插件都不应该在 jQuery.fn 对象中声明超过一个命名空间。

(function( $ ){

  $.fn.tooltip = function( options ) { 
    // 这样 THIS
  };
  $.fn.tooltipShow = function( ) {
    // 做 IS
  };
  $.fn.tooltipHide = function( ) { 
    // 很不好 BAD
  };
  $.fn.tooltipUpdate = function( content ) { 
    // !!!
  };

})( jQuery );

我们不提倡这样做,因为它会使 $.fn 命名空间变得混乱。要避免这样的问题,你应该将所有插件的方法收集到一个对象中,然后通过将方法的字符串名称传递给插件来调用它们。

(function( $ ){

  var methods = {
    init : function( options ) { 
      // THIS 
    },
    show : function( ) {
      // IS
    },
    hide : function( ) { 
      // GOOD
    },
    update : function( content ) { 
      // !!! 
    }
  };

  $.fn.tooltip = function( method ) {

    // Method calling logic
    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 );

// calls the init method
$('div').tooltip(); 

// calls the init method
$('div').tooltip({
  foo : 'bar'
});
// calls the hide method
$('div').tooltip('hide');
// calls the update method
$('div').tooltip('update', 'This is the new tooltip content!');

这种类型的插件架构能够让你把所有方法封装在插件的父闭包中,然后通过先传递方法名,接着传递其他的一些需要用到的参数来调用。这种方法封装的方式和架构在 jQuery 插件社区是一个标准,被无数插件采用,包括 jQueryUI 中的插件和微件。

6.2 事件 Events

bind 方法的一个不是那么知名的用法是允许绑定事件命名空间。如果你的插件绑定了一个事件,一个很好的处理方式是给它进行空间命名。通过这种方式,如果你之后需要将它解除绑定(unbind),就可以在不影响可能绑定到同一事件类型的其它事件的情况下轻松解除绑定。如果要给你的事件命名空间,可以通过给需要绑定的事件类型附加“.<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 );
$('#fun').tooltip();
// Some time later...
$('#fun').tooltip('destroy');

在这个例子中,当 tooltip 通过 init 方法初始化之后,它就会绑定 reposition 方法到 window 对象的 resize 事件,这些都是在 ‘tooltip’ 命名空间下进行的。之后,如果开发人员需要销毁 tooltip,就可以通过传递它的命名空间来解除由插件绑定的事件。这使得我们可以安全的解除绑定插件绑定的事件而不会一不小心就将可能已经绑定到插件之外的事件同时解除绑定。

6.3 数据 Data

在插件的开发中,你可能经常需要去维护状态,或者检查插件是否已经在一个指定的元素上被初始化了。使用 jQuery 的 data 方法就能够在每个元素上跟踪变量。可是与跟踪大量有着不同名称的独立数据调用相比,使用一个单一对象来放置所有变量,然后通过单一数据命名空间来存取此对象才是最佳的实践方式。

(function( $ ){

  var methods = {
     init : function( options ) {

       return this.each(function(){

         var $this = $(this),
             data = $this.data('tooltip'),
             tooltip = $('<div />', {
               text : $this.attr('title')
             });

         // If the plugin hasn't been initialized yet
         if ( ! data ) {

           /*
             Do more setup stuff here
           */

           $(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 );

通过使用 data 能够帮助你跟踪插件各个方法调用之间的变量和状态。把你的数据命名空间约束到一个对象中,通过一个中心化的位置轻松访问你的所有插件的属性,同时还可以轻松地移除不需要的数据以缩减数据命名空间。

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

开发使用 jQuery 插件能够让你最大限度的使用库,将自己的天才想法和有用的函数抽象成可重用的代码,从而节省大量的时间,也使你的开发工作效率更高。下面是关于本文的一个简单总结,同时也指出了在开发 jQuery 插件时应该时刻牢记的一些准则:

  • 始终把你的插件封装在一个闭包中:
    (function( $ ){ /* 插件内容 */ })( jQuery );
  • 不要在插件函数的当前范围中重复封装 this 关键词。
  • 除非你的插件会返回一个内在值,否则你应该让自己的插件函数始终返回关键词 this 以维持可链通性。
  • 不要搞一长串参数,而是通过一个可根据插件默认设置进行扩展的对象来给插件传递设置。
  • 保证每个插件中 jQuery.fn 对象只有一个命名空间。
  • 始终给自己的方法、事件和数据声明命名空间。

8. 后记

最近给水景一页添加修改一些 jQuery 效果的时候遇到一些问题,遂阅读了 jQuery 上的这篇入门文章,同时简单翻译了一篇中文以方便查阅。如果有什么地方翻译得不准确,请留言指正。

原文:http://docs.jquery.com/Plugins/Authoring©

本文发表于水景一页。永久链接:<https://cnzhx.net/blog/jquery-plugins-authoring/>。转载请保留此信息及相应链接。

时间过去太久,评论已关闭。
如果您有话要说,请到讨论区留言并给出此文章链接。
谢谢您的理解 :-)