A-A+

用 jQuery 编写第三方插件

2016年06月01日 JavaScript 暂无评论 阅读 254 次

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

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

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

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

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

jQuery 全局函数

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

给 jQuery 对象新加一个属性

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

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

用 $.extend()

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

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

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

封装在一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function($){
$.mathUtils = {
sum: function(){
//do something
},
average: function(){
//do something
}
};
})(jQuery);
//使用
$.mathUtils.sum();
$.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
3
4
5
6
7
//定义
jQuery.fn.myMethod = function(){
console.log('Hello World');
};
//使用
$('div').myMethod();

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

对象方法的上下文

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

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

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

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

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

隐式迭代 + 方法连缀

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

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

方法参数

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

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

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

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

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

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

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是怎么写的呢】
可定制的默认值:话说,应用的真实场景-没找到一个合适的。感觉….把默认值暴露在外面了,也不安全 | 也破坏了封装性。'

原文地址:http://anjia.github.io/2015/09/17/jq_plugin_dev/

标签:

给我留言

Copyright © web前端技术开发个人博客 保留所有权利  京ICP备14060653号 Theme  Ality

用户登录