jQuery插件开发

总感觉自己的jQuery用的特别流利。分分钟就能选择一个DOM元素,让页面改头换面。可让自己写个东西的时候,又写的很是别扭。比如要写一个插件,分分钟觉得脑子里一团浆糊。从这捡点概念,从那拿点技巧,总是形不成系统的认识。

好了,索性就花点心思把jQuery插件开发梳理一遍,让脑子里的浆糊变成一碗香喷喷的面。

$.extend() 和 $.fn.extend()

用jquery开发插件,用到的核心方法有两个:

1、$.extend() —— 为jquery添加静态方法。如我们经常用到的$.each( object, callback )迭代方法,即静态方法。
2、$.fn.extend() —— 为jquery添加实例方法。如$('.yunmo').css('color','#000'),选择一个类名为yunmo的DOM元素,所形成的$('.yunmo')实例应用css()方法。

基本用法如下:

$.extend()
$.extend({
  learn: function () {
    console.log('I am learning coding');
  },
  practice: function () {
    console.log('I am translating my coding skills into something visible!');
  }
});

$.learn();
$.practice();
$.fn.extend()
$.fn.extend({
   learn: function () {
     console.log('I am learning coding');
   },
   practice: function () {
      console.log('I am translating my coding skills into something visible!');
   }
});

//以上定义方式等同于

$.fn.learn = function () {
   console.log('I am learning coding');
};
$.fn.practice = function () {
   console.log('I am translating my coding skills into something visible!');
};

$('.yunmo').learn();
$('.yunmo').practice();
这里有必要来解释一下$.extend()方法。

通常,jQuery.extend()函数用于将一个或多个对象的内容合并到目标对象,即该函数可以将一个或多个对象的成员属性和方法复制到指定的对象上。属于全局jQuery对象。有以下两种调用方式。

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

jQuery.extend( [ deep ], target , object1 [, objectN... ] );
  1. deep:可选/Boolean类型指示是否深度合并对象,默认为false。如果该值为true,且多个对象的某个同名属性也都是对象,则该"属性对象"的属性也将进行合并。
  2. target:Object类型目标对象,其他对象的成员属性将被复制到该对象上。
  3. object1:可选/Object类型第一个被合并的对象。
  4. objectN: 可选/Object类型第N个被合并的对象。

说明:

  • 该函数复制的对象属性包括方法在内。此外,还会复制对象继承自原型中的属性(JS内置的对象除外)。
  • 参数deep的默认值为false,你可以为该参数明确指定true值,但不能明确指定false值。简而言之,第一个参数不能为false值。
  • 如果参数为null或undefined,则该参数将被忽略。
  • 如果只为$.extend()指定了一个参数,则意味着参数target被省略。此时,target就是jQuery对象本身。通过这种方式,我们可以为全局对象jQuery添加新的函数。
  • 如果多个对象具有相同的属性,则后者会覆盖前者的属性值。
var obj1 = {name: 'yun', age: '18', skills: {amount: 10, best: 'coding'}};
var obj2 = {name: 'mo', place: 'China', skills: {amount: 8, worst: 'design'}};

//console.log($.extend(obj1, obj2)); 
//{name: "mo", age: "18", skills: {worst:"design"}, place: "China"}
console.log($.extend(true, obj1, obj2));  
//{name: "mo", age: "18", skills: {amount: 10, best: "coding", worst:"design"}, place: "China"}

以上两句请互斥打印查看,否则控制台会缓存上句打印处理,导致第二句打印不符合预期。

如上述说明中所说的:如果只为$.extend()指定了一个参数,则意味着参数target被省略。此时,target就是jQuery对象本身。通过这种方式,我们可以为全局对象jQuery添加新的函数。

即:

$.extend({
   learn: function () {
     console.log('I am learning coding');
   },
   practice: function () {
     console.log('I am translating my coding skills into something visible!');
   }
});

可以看到,通过$.extend()模式只能为jQuery类添加简单的静态方法,无法进行DOM操作,因此我们需要借助$.fn.extend()模式。

如前面所述,这种模式有两种写法。

即:

$.fn.extend({
   learn: function () {
     console.log('I am learning coding');
   }
});

或

$.fn.learn = function () {
   console.log('I am learning coding');
};

为什么呢?我们来看看源码:

jQuery.fn = jQuery.prototype = {
  init:function(selector,context){...};
};

这样就一目了然了,jQuery.fn其实和jQuery的原型指向的是同一对象。那么$.fn.extend()就相当于对jQuery.prototype对象进行扩展(原型也是对象),又因为只传递了一个参数,那么这个target就是jQuery.prototype本身。故两种写法都可以。$.fn.extend()的通过把方法扩展到对象的jQuery的prototype上,从而实例化一个jQuery对象时,它就具有了这些方法(得益于原型继承)。

那么便有如下等式成立:

$.fn.extend(plugin) == $.prototype.extend(plugin) == $.fn.plugin

综合实例

(function ($) {
   var Paint = function (element, options) {
    this.$element = $(element);
    this.options = options;
   };

   Paint.defaults = {
     tools: 'pencil',
     color: 'red'
   };

   Paint.prototype = {
     prepare: function () {
       var tools = this.options.tools;
       this.$element.html('I am painting with '+ tools);
     },
     getStarted: function () {
       var color = this.options.color;
       this.$element.css({'color': color, 'fontSize': '20px'});
     },
     init: function () {
       this.prepare();
       this.getStarted();
     }
 };

 $.fn.paint = function (options) {
    return this.each(function () {    //支持链式调用
       var opt = $.extend({}, Paint.defaults, options); // 支持自定义参数
       var paint = new Paint(this, opt);
       paint.init();
    })
  }
})(jQuery);

$('.yun').paint();
$('.mo').paint({color: 'blue'});
FAQ:
1、为什么要像下面这样,把插件包在(function($){})(jQuery)里面?
(function($){
   //plugin code goes here......
})(jQuery);

答:插件封装和定义作用域:避免插件内部变量污染全局,不对外暴露插件内部的实现;解耦插件逻辑和运行环境之间的依赖。

2、为何插件要return this.each(function(){})?

答:这样支持链式调用。return this.each(function(){})的this,指代jQuery选择器返回的集合,通过jQuery.each()方法便能够遍历集合中的元素进行相应逻辑处理。而在each方法内部,this则指代普通的DOM元素,需要用$(this)来调用jQuery方法,即在this.$element.html('I am painting with '+ tools)这里有所体现。

若要支持链式调用,再加上return即可。因为each()方法的返回值为jQuery类型,返回当前jQuery对象本身,这样就能把each方法返回的jQuery对象返回,就可以继续调用jQuery的其他方法了。就像这样;

$('.mo').paint({color: 'blue'}).html('wooooo, it is overridden by another jQuery method!');
3、为何不直接向下面代码这样定义,而要重新new 一个Paint实例?
(function ($) {
   $.fn.paint = function (options) {
      var defaults = {
       name: 'yun',
       age: '18'
    };
    var opt = $.extend({}, defaults, options);
      return this.each(function () {
         //.....
    })
   }
})(jQuery);

答:在对象上直接定义重要属性和私有方法,这样一来,在对象的方法中,我们可以调用对象的其他私有方法和访问私有变量。如果以后要扩展新功能,只需向对象添加新的变量和私有方法即可。

上例我们在Paint.prototype原型对象上定义了如prepare和getStarted、init私有方法,并定义了一个init方法来调用其他两个方法。最后让$.fn.paint专注于插件的调用,而不用关注插件的内部实现。这样的代码简洁明了,易于维护。

你可能感兴趣的:(jQuery插件开发)