A-A+

用 jQuery 编写第三方插件

2016年09月08日 jquery 暂无评论 阅读 221 次

用 jQuery 编写第三方插件,常见的三种方式:

  1. 新的全局函数 $.xxx()
  2. jQuery 的对象方法 $(‘div’).xxx()
  3. jQuery UI 的部件工厂:少量编码,复杂插件

本节重点介绍前两种。插件的基本结构如下:

  1. //传参 + IIFE
  2. (function($){
  3. //do something
  4. })(jQuery);

目的:保证 $ 不会有冲突
原因:由于代码中的其他部分可能会让渡快捷键 $ 的使用权,即 $.noConflict();
所以,在自定义插件时,最好始终都用 jQuery,然后在内部定义一个别名 $。定义一个函数,立马调用它(立即调用的函数表达式 IIFE),这样就不会有 $ 的冲突了。

jQuery 全局函数

jQuery 的全局函数,即位于 jQuery 命名空间内部的函数。在核心 jQuery 库里,有很多全局函数,都是实用方法(实用方法,即一些常用功能的快捷方式),比如常见的 $.ajax()、$.each().map().get()、$.find()、$.grep() 等。放在 jQuery 的命名空间内,只需避免和 jQuery 其他的方法名冲突即可。下面介绍三种方式。

给 jQuery 对象新加一个属性

给 jQuery 的命名空间中添加一个函数,只需给 jQuery对象新添加一个属性。

  1. //定义
  2. (function($){
  3.     $.sum = function(){
  4. //do something
  5.     };
  6. })(jQuery);
  7. //使用,和基本的调用一样
  8. $.sum();

用 $.extend()

还可以通过另外一种语法来定义全局函数,利用 $.extend()

  1. (function($){
  2.     $.extend({
  3.         sum: function(){
  4. //do something
  5.         },
  6.         average: function(){
  7. //do something
  8.         }
  9.     });
  10. })(jQuery);
  11. //使用
  12. $.sum();
  13. $.average();

当然,这也有可能污染命名空间,即和 jQuery 其他的插件冲突。
解决:最好把属于一个插件的全局函数都封装在一个对象中。

封装在一个对象

  1. (function($){
  2.     $.mathUtils = {
  3.         sum: function(){
  4. //do something
  5.         },
  6.         average: function(){
  7. //do something
  8.         }
  9.     };
  10. })(jQuery);
  11. //使用
  12. $.mathUtils.sum();
  13. $.mathUtils.average();

该模式的本质:为所有的全局变量又创建了一个命名空间。虽然还称它们为全局函数,但实际上它们已经是 mathUtils 对象的方法了,而 mathUtils 对象则保存在 jQuery 对象的属性中。

小结

一般情况下,不把函数保存在 jQuery 命名空间里,而是选择一个我们自己的全局对象。比如可以把 OB 作为全局对象,那么 $.mathUtils.sum() 就要写成 OB.mathUtils.sum() 了。这样,就可以彻底避免自定义插件和第三方插件方法命名上的冲突了。这种技术+足够有特色的命名空间,可在一定程度上避免全局函数的污染命名空间。这,就是开发插件的基本方法。

以上呢,都是组织上的好处,着眼于如何保护命名空间。真正体验 jQuery 插件的威力,是为个别 jQuery 对象实例创建新的方法。来,下面继续。

jQuery 对象的方法

前面介绍的添加全局函数,以新方法来扩展 jQuery 对象
添加实例方法,扩展的是 jQuery.fn 对象。jQuery.fn 对象是 jQuery.prototype 的别名,用别名是为了简洁。

jQuery 大多数内置的功能,都是通过对其对象实例的方法提供的,使用这个方法是插件之所以诱人的关键。eg.当函数需要操作 Dom 元素时,就是将函数创建成 jQuery对象方法的好机会。首先,我们来举个反例:

  1. //定义
  2. jQuery.fn.myMethod = function(){
  3.     console.log('Hello World');
  4. };
  5. //使用
  6. $('div').myMethod();

结果是:当页面中有多个 div 时只会输出一次;当页面中没有 div 时,依然也会输出一次。我们会发现,这么使用并没有什么卵用。所以呢,一个合理的实例方法,应该包含它对上下文的操作,即有操作匹配到的 DOM 结构。

对象方法的上下文

举例:写一个调换两个类名的方法 .swapClass()

  1. //定义
  2. (function($){
  3.     $.fn.swapClass = function(class1, class2){
  4. // this 引用当前 jQuery 对象 
  5. if(this.hasClass(class1)){
  6. this.removeClass(class1).addClass(class2);
  7.         }else if(this.hasClass(class2)){
  8. this.removeClass(class2).addClass(class1);
  9.         }
  10.     };
  11. })(jQuery);
  12. //使用
  13. $('.demo').swapClass('one','two');

 

在插件方法的内部,this 引用当前的 jQuery 对象。所以,我们可以在 this 上调用任何内置的 jQuery 方法,或者提取它包含的 DOM 节点并操作该节点。

问题是:不论有没有元素被选中(当元素是空[] 或者多个元素时),它依然会显示一次,仅一次。而且,在上面的代码中,在调用 .hasClass() 时只会检查匹配到的第一个元素。

我们知道,jQuery 选择符表达式可能匹配0到多个元素,所以,在设计插件时,要考虑这些情况。要解决上面的问题,我们要独立检查和操作每一个元素,无论匹配多少个时都可以行为正确。最简单的方法是:始终在方法的上下文调用 .each() 方法。这样,就会执行隐式迭代。隐式迭代对维护插件与内置方法的一致性至关重要。

隐式迭代 + 方法连缀

隐式迭代:始终在方法上调用 .each()
方法连缀:在插件的方法中返回 jQuery 对象

  1. //定义
  2. (function($){
  3.     $.fn.swapClass = function(class1, class2){
  4. //在插件方法内,this 引用一个 jQuery 对象
  5. return this.each(function(){
  6. //在 each 内,this 引用一个 DOM 元素
  7.             var me = $(this);
  8. if(me.hasClass(class1)){
  9.                 me.removeClass(class1).addClass(class2);
  10.             }else if(me.hasClass(class2)){
  11.                 me.removeClass(class2).addClass(class1);
  12.             }
  13.         });
  14.     };
  15. })(jQuery);
  16. //使用
  17. $('.demo').swapClass('one','two');

方法参数

在参数层面,我们需要注意以下4点:

  1. 定义恰当的默认值
  2. 允许覆盖默认值:给插件传参,向插件用户公开选项的方式
  3. 回调函数:极大增加插件的灵活性,但却不用在创建插件时编写过多代码
  4. 默认值可定制

eg1.
给一个元素添加投影,有覆盖到上面提到的 1-2-3 点

  1. (function($){
  2.     $.fn.shadow = function(opts){
  3. //1. 定义恰当默认值
  4.         var defaults = {
  5.             copies: 5,
  6.             opacity: 0.1,
  7.             copyOffset: function(index){  //3. 接受一个函数对象作为参数-让用户自定义投影相对于文本的位置
  8. return {
  9.                     x: index,
  10.                     y: index
  11.                 }
  12.             }
  13.         };
  14. //2. 允许覆盖默认值
  15.         var options = $.extend(defaults, opts);
  16. return this.each(function(){
  17.             var ori = $(this);
  18. for(var i=0; i<options.copies; i++){
  19.                 var offset = options.copyOffset(i); //3. 调用回调,取得投影相对于文本的位置
  20.                 ori.clone()
  21.                    .css({
  22.                         position: 'absolute',
  23.                         left: ori.offset().left+offset.x,
  24.                         top: ori.offset().top+offset.y,
  25.                         margin: 0,
  26.                         zIndex: -1,
  27.                         opacity: options.opacity
  28.                    })
  29.                    .appendTo('body');
  30.             }
  31.         });
  32.     };
  33. })(jQuery);
  34. //使用:不传参
  35. $('h1').shadow();
  36. //使用:部分参数
  37. $('h2').shadow({
  38.     copies: 3,
  39.     opacity: 0.5
  40. });
  41. //使用:回调函数,简单地修改投影方向-根据插件用户的定义
  42. $('h3').shadow({
  43.     copyOffset: function(i){
  44. return {
  45.             x: -i,
  46.             y: -2*i
  47.         };
  48.     }
  49. });

eg2. 可定制的默认值
上面第1点有提到“定义恰当的默认值”,但是,没有什么默认值是真正恰当的。试想这种场景:如果脚本中多次调用了我们的插件,若要统一修改该插件的参数,那么,就需要在每次调用时都传递一组参数值-去覆盖插件中写死的默认参数。
这,就是定制默认值的应用场景,它可以减少很多代码量。

  1. (function($){
  2.     $.fn.shadow = function(opts){
  3. //4. 启用全局默认:为了不覆盖其他值-故使用了 {}
  4.         var options = $.extend({}, $.fn.shadow.defaults, opts);
  5. return this.each(function(){
  6. //同上,此处省略...
  7.         });
  8.     };
  9. })(jQuery);
  10. //在定义插件的外面,统一设置默认值
  11. $.fn.shadow.defaults = {
  12.     copies: 5,
  13.     opacity: 0.1,
  14. };

others

CSS3 选择器
td:nth-child(2) css3选择器 其父元素-的第N个子元素
tr:nth-child(odd)
tr:nth-child(even)

jQuery的全局函数
.map() 后是数组
.get()
.toFixed()
//链式调用用起来果然和读英语一样,前提是用的炉火纯青

.clone()
.appendTo() //方便链式调用哦~
.css()
【?? 彻底~~??不还是在$上挂着么,只是换了个别名嘛…】
通过插件模仿jQuery API,用映射来提高一致性和易用性 【那个啥,好想知道-jQuery 自带的API是怎么写的呢】
可定制的默认值:话说,应用的真实场景-没找到一个合适的。感觉….把默认值暴露在外面了,也不安全 | 也破坏了封装性。

 

ps:http://anjia.github.io/2015/09/17/jq_plugin_dev/

标签:

给我留言

Copyright © web前端技术开发个人博客 保留所有权利.   Theme  Ality

用户登录