扩展 jQuery 有两种形式:

  • 直接定义在 $ 上的实用函数
  • 操作 jQuery 包装集的方法

1. jQuery插件开发指南

1.1 为文件和函数命名

  • 为文件名添加jquery前缀
  • 前缀后面是插件的名称
  • 包含插件的主版本号和次版本号(可选项)
  • 以.js结束

1.2 当心 $

(function($){

  //

  // 插件定义

  //

})(jQuery);

1.3 简化参数列表

使用 options 散列值。

例如:complex(valueA, {p7: valueB});

complex(valueA, {

  p3: valueC,

  p4: valueD,

  p7: valueB

});

简化后的函数签名:complex(p1, options)

在函数中,使用 $.extend() 将这些选项与默认值合并:

function complex(p1, options) {

  var settings = $.extend({

    option1: defaultValue1,

    option2: defaultValue2,

    option3: defaultValue3

  }, options || {});

  // 函数其他部分

}

2. 编写自定义实用函数

实用函数是定义为 jQuery($)属性的函数,它不是为了操作DOM元素。

$.say = function(what) { alert('I say ' + what); };

也可以:jQuery.say = function(what) { alert('I say ' + what); };

最安全的写法:

(function($){

  $.say = function(what) { alert('I say ' + what); };

})(jQuery);

2.1 创建数据操作的实用函数

创建这样的实用函数:$.toFixedWidth(value, length, fill)

(function($) {

  $.toFixedWidth = function(value, length, fill) {

    var result = (value || '').toString();

    fill = fill || '0';

    var padding = length - result.length;

    if (padding < 0) {

      result = result.substr(-padding);

    } else {

      for (var n = 0; n < padding; n++)

        result = fill + result;

    }

    return result;

  };

})(jQuery);

为实用函数添加命名空间:

$.jQiaDataFormatter = {};

$.jQiaDataFormatter.toFixedWidth = function(value, length, fill){...};

2.2 编写日期格式器

语法:$.formatDate(date, pattern)

yyyy

yy

MMMM

MMM

MM

M

dd

d

EEEE

EEE

a

HH

H

hh

h

mm

m

ss

s

S

(function($){
  $.formatDate = function(date,pattern) {
    var result = [];
    while (pattern.length>0) {
      $.formatDate.patternParts.lastIndex = 0;
      var matched = $.formatDate.patternParts.exec(pattern);
      if (matched) {
        result.push($.formatDate.patternValue[matched[0]].call(this,date));
        pattern = pattern.slice(matched[0].length);
      }
      else {
        result.push(pattern.charAt(0));
        pattern = pattern.slice(1);
      }
    }
    return result.join('');
  };

  $.formatDate.patternParts =
    /^(yy(yy)?|M(M(M(M)?)?)?|d(d)?|EEE(E)?|a|H(H)?|h(h)?|m(m)?|s(s)?|S)/;

  $.formatDate.monthNames = [
    'January','February','March','April','May','June','July',
    'August','September','October','November','December'
  ];

  $.formatDate.dayNames = [
    'Sunday','Monday','Tuesday','Wednesday','Thursday','Friday',
    'Saturday'
  ];

  $.formatDate.patternValue = {
    yy: function(date) {
      return $.toFixedWidth(date.getFullYear(),2);
    },
    yyyy: function(date) {
      return date.getFullYear().toString();
    },
    MMMM: function(date) {
      return $.formatDate.monthNames[date.getMonth()];
    },
    MMM: function(date) {
      return $.formatDate.monthNames[date.getMonth()].substr(0,3);
    },
    MM: function(date) {
      return $.toFixedWidth(date.getMonth()+1,2);
    },
    M: function(date) {
      return date.getMonth()+1;
    },
    dd: function(date) {
      return $.toFixedWidth(date.getDate(),2);
    },
    d: function(date) {
      return date.getDate();
    },
    EEEE: function(date) {
      return $.formatDate.dayNames[date.getDay()];
    },
    EEE: function(date) {
      return $.formatDate.dayNames[date.getDay()].substr(0,3);
    },
    HH: function(date) {
      return $.toFixedWidth(date.getHours(),2);
    },
    H: function(date) {
      return date.getHours();
    },
    hh: function(date) {
      var hours = date.getHours();
      return $.toFixedWidth(hours>12 ? hours - 12 : hours,2);
    },
    h: function(date) {
      return date.getHours()%12;
    },
    mm: function(date) {
      return $.toFixedWidth(date.getMinutes(),2);
    },
    m: function(date) {
      return date.getMinutes();
    },
    ss: function(date) {
      return $.toFixedWidth(date.getSeconds(),2);
    },
    s: function(date) {
      return date.getSeconds();
    },
    S: function(date) {
      return $.toFixedWidth(date.getMilliseconds(),3);
    },
    a: function(date) {
      return date.getHours() < 12 ? 'AM' : 'PM';
    }
  };

  $.toFixedWidth = function(value,length,fill) {
    var result = (value || '').toString();
    fill = fill || '0';
    var padding = length - result.length;
    if (padding < 0) {
      result = result.substr(-padding);
    }
    else {
      for (var n = 0; n < padding; n++) result = fill + result;
    }
    return result;
  };

})(jQuery);

3. 添加新的包装器方法

创建模式:$.fn.wrapperFunctionName = function(params){ fucntion-body };

示例:

(function($){

  $.fn.makeItblue = function() {

    return this.css('color', 'blue');

  };

})(jQuery);

注意:$.fn 是对象内部 prototype 属性的别名。

警告:包装器主体部分中的函数上下文(this)指向包装集,但是当在这个函数内声明内联函数时,每一个内联函数都有其自己的函数上下文。这时使用 this 要小心!

在定义新的包装器方法时还有一个非常重要的原则:除非要返回特定的值,否则函数应该总是将包装集作为其返回值。这允许新方法参与到任何 jQuery 方法链中。

单独处理每个包装元素的模式:

(function($) {

  $.fn.someNewMethod = function() {

    return this.each(function() {

      //

      // this 指向单个的DOM元素

      //

    });

  };

})(jQuery);

示例:

(function($) {

  $.fn.makeItBlueOrRed = function() {

    return this.each(function() {

      $(this).css('color', $(this).is('[id]') ? 'blue' : 'red');

    });

  };

})(jQuery);

这个例子只是用来演示,实际上可以简化为:

(function($) {

  $.fn.makeItBlueOrRed = function() {

    return this.css('color', function() {

      return $(this).is('[id]') ? 'blue' : 'red');

    });

  };

})(jQuery);

3.1 在包装器方法中应用多个操作

示例:setReadOnly(state) state为true,将文字不透明度设置为0.5表示只读。

(function($){

  $.fn.setReadOnly = function(readonly) {

    return this.filter('input:text')

      .attr('readOnly', readonly)

      .css('opacity', readonly ? 0.5 : 1.0)

      .end();

  };

})(jQuery);

3.2 保留在包装器方法里的状态

方法语法:photomatic(options)

options:

  firstControl

  lastControl

  nextControl

  photoElement

  playControl

  previousControl

  transformer

  delay - 毫秒,默认3000

用法:

$('#thumbnailsPane img').photomatic({

  photoElement: '#photoDisplay',

  previousControl: '#previousButton',

  nextControl: '#nextButton',

  firstControl: '#firstButton',

  lastControl: '#lastButton',

  playControl: '#playButton',

  delay: 1000

});

实现:

(function($){

  $.fn.photomatic = function(options) {
    var settings = $.extend({
      photoElement: 'img.photomaticPhoto',
      transformer: function(name) {
                     return name.replace(/thumbnail/,'photo');
                   },
      nextControl: null,
      previousControl: null,
      firstControl: null,
      lastControl: null,
      playControl: null,
      delay: 3000
    },options||{});

    function showPhoto(index) {
      $(settings.photoElement)
        .attr('src',
              settings.transformer(settings.thumbnails$[index].src));
      settings.current = index;
    }

    settings.current = 0;
    settings.thumbnails$ = this.filter('img');
    settings.thumbnails$
      .each(
        function(n){ $(this).data('photomatic-index',n); }
      )
      .click(function(){
        showPhoto($(this).data('photomatic-index'));
      });

    $(settings.photoElement)
      .attr('title','Click for next photo')
      .css('cursor','pointer')
      .click(function(){
        showPhoto((settings.current+1) % settings.thumbnails$.length);
      });

    $(settings.nextControl).click(function(){
      showPhoto((settings.current+1) % settings.thumbnails$.length);
    });

    $(settings.previousControl).click(function(){
      showPhoto((settings.thumbnails$.length+settings.current-1) %
                settings.thumbnails$.length);
    });

    $(settings.firstControl).click(function(){
      showPhoto(0);
    });

    $(settings.lastControl).click(function(){
      showPhoto(settings.thumbnails$.length-1);
    });

    $(settings.playControl).toggle(
      function(event){
        settings.tick = window.setInterval(
          function(){ $(settings.nextControl).triggerHandler('click'); },
          settings.delay);
        $(event.target).addClass('photomatic-playing');
        $(settings.nextControl).click();
      },
      function(event){
        window.clearInterval(settings.tick);
        $(event.target).removeClass('photomatic-playing');
      });

    showPhoto(0);
    return this;
  };

})(jQuery);