0%

jQuery插件开发

一、开始

写一个插件,首先是要往 jquery.fn 对象中添加一个由你命名的function对象

1
2
3
jQuery.fn.myPlugin = function() {
// code
};

如果要使用我们熟悉的 $ 符号,并不希望它与其他脚本库冲突,那么如下

1
2
3
4
5
(function( $ ) {
$.fn.myPlugin = function() {
// code
};
})( jQuery );

现在我们就能用美元符号代替那个麻烦的“jQuery”了

二、背景

现在我们可以开始开发我们自己的插件了,但在此之前,我们必须注意一点,在我们所编写的插件function的作用域内,this关键字所代表的是调用该插件的jquery对象。

这是一个常见的错误,因为在jquery的回调函数里this关键字往往是表示一个DOM元素,这导致this关键字总被过度封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function( $ ){
$.fn.myPlugin = function() {

// 不需要写成$(this)是因为这里的this已经是一个jquery对象

// $(this) 代表的意思则变成了 $($('#element'));

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

// 这里的this是代表一个DOM元素

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

三、基础知识

现在我们了解了jquery插件的背景,现在我们写一个小demo来看看插件是怎么实现的

1
2
3
4
5
6
7
8
9
10
11
12
(function( $ ){
$.fn.maxHeight = function() {

var max = 0;

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

return max;
};
})( jQuery );
1
var tallest = $('div').maxHeight(); // 返回最高的div的height

这是一个简单的例子

四、保持链接性

前面的例子返回了一个div的高度值,但通常插件是简单的修改集合的元素,并通过链将元素传递到下一个方法执行。

所以为了保持连接性,我们必须确保我们的插件将this关键字返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(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 );
1
$('div').lockDimensions('width').css('color', 'red');

因为插件在它的作用域内返回了this关键字,使它保持了关联性并且使其他jquery方法可以继续操作这些jquery集合,例如.css。

所以你的插件如果不返回一个固有值,你应该在你插件function的作用域里返回this关键字。

此外,如你所想的,你传递给你插件调用的参数获得了你插件function的作用域(好吧。。。我承认这句不太会翻。。。大概意思就是传进来的参数在作用域里都可用吧,废话@_@~!)。

所以在前面的例子中,字符串‘width’在插件函数中变为了参数‘type’。

五、默认值(defaults) 和 配置(options)

对于更复杂的和可配置的插件提供了许多options,当插件调用时,具有可扩展的默认设置(用$.extend)是一种最好的实现方法。

所以与其调用的插件拥有大量的参数,不如调用这个插件时只有一个参数,但这个参数是一个可重写设置的object对象。

这里是如何实现的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function( $ ){
$.fn.tooltip = function( options ) {

// 创建一些默认值, 用提供的options来扩展
var settings = $.extend( {
'location' : 'top',
'background-color' : 'blue'
}, options);

return this.each(function() {
// code
});
};
})( jQuery );
1
2
3
$('div').tooltip({
'location' : 'left'
});

在这个例子里,用给定的options调用tooltip插件之后,默认的location设置被重新设置为‘left’,而 background-color还是默认的‘blue’。

所以最终的settings对象应该是:

1
2
3
4
{
'location' : 'left',
'background-color' : 'blue'
}

这是一个伟大的方式,提供了一个不需要开发人员定义所有有效options的高度可配置插件。

六、命名空间(namespace)

适当的命名空间对于插件开发是一个非常重要的部分。

正确的命名空间可以确保你的插件只有极小的可能被同一个page的其他插件或者代码给覆盖。

命名空间可以让你作为插件开发者的生活更轻松,因为它能帮助你更好的跟踪你的method、event和数据。

6.1 插件的方法(method)

在任何情况下一个单独的插件都要求在jquery.fn对象里有一个以上的命名空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function( $ ){

$.fn.tooltip = function( options ) {
// THIS
};
$.fn.tooltipShow = function( ) {
// IS
};
$.fn.tooltipHide = function( ) {
// BAD
};
$.fn.tooltipUpdate = function( content ) {
// !!!
};

})( jQuery );

这个是不好的,因为它使jquery.fn命名空间变得杂乱。

为了解决这个问题,你应该收集你所有的插件里的方法到一个object对象,并且通过传递这个方法的name字符串到插件来调用它们。

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
(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'
});
1
2
// calls the hide method
$('div').tooltip('hide');
1
2
// calls the update method
$('div').tooltip('update', 'This is the new tooltip content!');

这种类型的插件架构允许你将你所有的方法封装在插件的父封闭(父作用域?)中,并首先传入方法名称字符串来调用它们,然后为这些方法传入你所需要的参数。

在jquery plugin社区这类方法的封装和结构是一个标准,它被无数插件所使用,包括了jqueryUI的插件和部件。

6.2 事件(Event)

一个鲜为人知的绑定方法的特色是允许命名已绑定的方法。 如果你的插件绑定了一个事件,一个很好的方法是命名它(意思应该就是例子中那种通过命名空间方法传事件名字符串来调用吧?!)

这样,如果你以后要解除绑定(unbind),这样做可以排除可能绑定到同一个事件类型的其他事件的干扰。

你可以通过追加(appending)“.”到你绑定的事件类型的方法来命名你的事件。

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
(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 );
1
2
3
$('#fun').tooltip();
// Some time later...
$('#fun').tooltip('destroy');

在这个例子中,当tooltip被init方法初始化时,它在“tooltip”命名空间下将reposition方法绑定到了window的resize事件上。

之后,如果开发者需要销毁tooltip,我们可以通过传入它的命名到unbind方法将插件事件解除绑定(unbind),在这个例子里是“tooltip”。

这允许我们安全的解除事件绑定,排除了意外解除掉插件外绑定的事件的情况。

6.3 数据(data)

常常在插件开发的时候,如果你的插件已经初始化一个给定的元素,你需要保持它的状态或者检查它。

使用jquery的data方法是一个伟大的方法以保持对每一个基本元素的变量的跟踪。

然而,最好使用一个object对象容纳你所有的变量,并且通过一个单独的data命名去访问这个object,而不是持续跟踪一堆不同名称的分散的数据。

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
55
56
57
58
59
60
61
62
(function( $ ){

var methods = {
init : function( options ) {

return this.each(function(){

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

// 如果插件已经初始化
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帮助你在你的插件方法里跟踪变量和状态。命名你的数据到一个object对象,这样你就能很轻松的从一个中心位置访问到你插件的所有属性,并且能轻松取消你想要移除的数据命名。

七、总结和最佳做法

编写jquery插件能使利用高效的库(library),以及抽象最灵活有用的功能,使之成为可重用的代码,可以节省你的时间和使你的开发更高效。

下面是一个简单的总结,请在下一次开发jquery插件时牢记:

1、始终将您的插件放在一个闭包中:

1
(function( $ ){ /* plugin goes here */ })( jQuery );

2、在你的插件function的作用域里不要重复封装this关键字

3、除非你的插件要返回一个固定值,否则使插件总是返回this关键字以保持连续性

4、与其使用大量的参数,不如将你的设置传入一个可扩展你插件默认值的object对象中。

5、不要使一个插件具有超过1个命名空间,导致jquery.fn对象混乱。

6、始终命名(namespace)你的方法、事件和数据。

实例

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
(function($){
//使用extend
$.extend({
foo:function(){
alert('duo zhong plugin zu he');
},
bar1:function(param){
alert(param);
},
});

//使用命名空间
$.myplugin = {
foo:function(){
alert('duo zhong plugin zu he !');
},
bar1:function(param){
alert(param);
}
};

//对象级别的插件
//1.添加函数
$.fn.foo1=function(){
alert('dui xiang ji bie');
};

//2.添加extend
$.fn.extend({
foo1:function(){
alert('extend is all') ;
},
bar2:function(){
alert('没有参数');
}
});
})(jQuery);

//添加一个函数
$.bar = function(param){
alert(param);
}