如何创建一个基本的插件
有时您希望在整个代码中提供一些功能。例如,也许你想要一个单一的方法,你可以调用一个jQuery选择,对选择执行一系列的操作。在这种情况下,您可能需要编写一个插件。
链接jQuery如何工作101:jQuery对象方法
在我们编写自己的插件之前,首先要了解一下jQuery如何工作。看看这段代码:
1
|
$( "a" ).css( "color", "red" ); |
这是一些很基础的jQuery代码,但你知道幕后发生了什么吗?无论何时使用该$
函数来选择元素,它返回一个jQuery对象。这个对象包含了所有的你已经使用(方法.css()
,.click()
等)和所有适合你的选择要素。jQuery对象从对象中获取这些方法$.fn
。这个对象包含了所有的jQuery对象方法,如果我们想编写自己的方法,它也需要包含这些方法。
链接基本插件创作
假设我们要创建一个插件,使一组检索到的元素中的文本变为绿色。我们所要做的就是添加一个调用的函数greenify
来$.fn
,这将是可用的,就像任何其他的jQuery对象的方法。
1
2
3
4
五
|
$.fn.greenify = function() { this.css( "color", "green" ); }; $( "a" ).greenify(); // Makes all the links green. |
注意使用.css()
,另一种方法,我们使用this
,而不是$( this )
。这是因为我们的greenify
函数是与之相同的对象的一部分.css()
。
链接链接
这是有效的,但是我们需要做的几件事情就是让我们的插件在现实生活中生存下去。当您将五个或六个操作链接到一个选择器上时,jQuery的功能之一就是链接。这是通过使所有jQuery对象方法再次返回原始的jQuery对象来实现的(有一些例外:.width()
调用无参数返回所选元素的宽度,并且不可链接)。使我们的插件方法可链接需要一行代码:
1
2
3
4
五
6
|
$.fn.greenify = function() { this.css( "color", "green" ); return this; } $( "a" ).greenify().addClass( "greenified" ); |
链接保护$别名和添加范围
该$
变量是JavaScript库中很受欢迎,如果你正在使用jQuery的另一个库,你将不得不作出的jQuery不使用$
带jQuery.noConflict()
。然而,这将会破坏我们的插件,因为它是用函数$
的别名假设来写的jQuery
。为了与其他插件很好地工作,并且仍然使用jQuery的$
别名,我们需要把我们所有的代码里面立即调用函数表达式,然后传递给函数jQuery
,并命名参数$
:
1
2
3
4
五
6
7
8
|
(function ( $ ) { $.fn.greenify = function() { this.css( "color", "green" ); return this; }; }( jQuery )); |
此外,立即调用函数的主要目的是允许我们拥有自己的私有变量。假装我们想要一个不同的颜色绿色,我们想把它存储在变量中。
1
2
3
4
五
6
7
8
9
10
|
(function ( $ ) { var shade = "#556b2f"; $.fn.greenify = function() { this.css( "color", shade ); return this; }; }( jQuery )); |
链接最小化插件足迹
编写插件时只需占用一个插槽就是很好的做法$.fn
。这样可以减少您的插件被覆盖的机会,并且插件将覆盖其他插件的机会。换句话说,这是坏的:
1
2
3
4
五
6
7
8
9
10
11
|
(function( $ ) { $.fn.openPopup = function() { // Open popup code. }; $.fn.closePopup = function() { // Close popup code. }; }( jQuery )); |
有一个插槽会更好,并使用参数来控制一个插槽执行什么操作。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
|
(function( $ ) { $.fn.popup = function( action ) { if ( action === "open") { // Open popup code. } if ( action === "close" ) { // Close popup code. } }; }( jQuery )); |
链接使用each()
方法
您的典型jQuery对象将包含对任意数量的DOM元素的引用,这就是为什么jQuery对象通常被称为集合。如果要对特定元素进行任何操作(例如获取数据属性,计算特定位置),则需要使用.each()
循环元素。
1
2
3
4
五
6
7
|
$.fn.myNewPlugin = function() { return this.each(function() { // Do something to each element here. }); }; |
请注意,我们返回结果.each()
而不是返回this
。既然.each()
已经是可链接的,它返回this
,然后我们返回。这是保持可链接性的一种更好的方法,而不是我们迄今为止所做的一切。
链接接受选项
随着您的插件越来越复杂,最好通过接受选项来使您的插件可自定义。最简单的方法是这样做,特别是如果有很多选项,那就是一个对象文字。我们更改我们的greenify插件以接受一些选项。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
(function ( $ ) { $.fn.greenify = function( options ) { // This is the easiest way to have default options. var settings = $.extend({ // These are the defaults. color: "#556b2f", backgroundColor: "white" }, options ); // Greenify the collection based on the settings variable. return this.css({ color: settings.color, backgroundColor: settings.backgroundColor }); }; }( jQuery )); |
使用示例
1
2
3
|
$( "div" ).greenify({ color: "orange" }); |
为默认值color
的#556b2f
获取通过重写$.extend()
为橙色。
链接在一起
这是一个使用我们讨论的一些技巧的小插件的例子:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
|
(function( $ ) { $.fn.showLinkLocation = function() { this.filter( "a" ).each(function() { var link = $( this ); link.append( " (" + link.attr( "href" ) + ")" ); }); return this; }; }( jQuery )); // Usage example: $( "a" ).showLinkLocation(); |
这个方便的插件遍历集合中的所有锚点,并将该href
属性附加在括号中。
1
2
3
4
五
|
<!-- Before plugin is called: --> <a href="page.html">Foo</a> <!-- After plugin is called: --> <a href="page.html">Foo (page.html)</a> |
我们的插件可以通过以下优化:
1
2
3
4
五
6
7
8
9
10
11
12
13
|
(function( $ ) { $.fn.showLinkLocation = function() { this.filter( "a" ).append(function() { return " (" + this.href + ")"; }); return this; }; }( jQuery )); |
我们使用该.append()
方法的能力来接受回调,并且该回调的返回值将决定附加到集合中每个元素的内容。还要注意,我们没有使用该.attr()
方法来检索href
属性,因为本机DOM API可以方便地访问aptly named href
属性。
高级插件概念
链接提供公共访问默认插件设置
我们可以而且应该对上面的代码进行改进是公开默认的插件设置。这很重要,因为它使插件用户很容易用最小的代码覆盖/定制插件。这就是我们开始利用函数对象的地方。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
|
// Plugin definition. $.fn.hilight = function( options ) { // Extend our default options with those provided. // Note that the first argument to extend is an empty // object – this is to keep from overriding our "defaults" object. var opts = $.extend( {}, $.fn.hilight.defaults, options ); // Our plugin implementation code goes here. }; // Plugin defaults – added as a property on our plugin function. $.fn.hilight.defaults = { foreground: "red", background: "yellow" }; |
现在,用户可以在脚本中包含这样一行:
1
2
3
|
// This needs only be called once and does not // have to be called from within a "ready" block $.fn.hilight.defaults.foreground = "blue"; |
现在我们可以像这样调用插件方法,它将使用蓝色的前景色:
1
|
$( "#myDiv" ).hilight(); |
您可以看到,我们允许用户编写一行代码来更改插件的默认前景色。而用户仍然可以选择性地覆盖这个新的默认值:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
|
// Override plugin default foreground color. $.fn.hilight.defaults.foreground = "blue"; // ... // Invoke plugin using new defaults. $( ".hilightDiv" ).hilight(); // ... // Override default by passing options to plugin method. $( "#green" ).hilight({ foreground: "green" }); |
链接提供公共访问次要功能作为适用
该项目与上一个项目紧密相关,是扩展插件(并允许其他扩展插件)的有趣方式。例如,我们的插件的实现可以定义一个称为“格式”的功能,其格式化高亮文本。我们的插件可能看起来像这样,默认实现的格式方法定义在hilight函数下面:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// Plugin definition. $.fn.hilight = function( options ) { // Iterate and reformat each matched element. return this.each(function() { var elem = $( this ); // ... var markup = elem.html(); // Call our format function. markup = $.fn.hilight.format( markup ); elem.html( markup ); }); }; // Define our format function. $.fn.hilight.format = function( txt ) { return "<strong>" + txt + "</strong>"; }; |
我们可以轻松地支持选项对象上的另一个属性,允许提供回调函数来覆盖默认格式。这是支持自定义插件的另一个很好的方法。这里显示的技术通过实际暴露格式功能使其可以重新定义,从而进一步扩展。使用这种技术,其他人可能会发布自己的插件自定义覆盖 - 换句话说,这意味着其他人可以为插件编写插件。
考虑到我们在本文中构建的简单的示例插件,您可能会想知道何时会有用。一个现实世界的例子是循环插件。循环插件是一个幻灯片插件,它支持多种内置的转换效果 - 滚动,滑动,淡入淡出等。但是,实际上,没有办法定义可能希望应用于幻灯片转换的每种类型的效果。这就是这种类型的可扩展性是有用的。循环插件公开了一个“transition”对象,用户可以添加自己的自定义转换定义。它在插件中定义如下:
1
2
3
4
五
|
$.fn.cycle.transitions = { // ... }; |
这种技术使得其他人可以定义和提供插件到循环插件的转换定义。
链接保持私人功能私有
暴露部分插件被覆盖的技术可以非常强大。但是,您需要仔细考虑实施的哪些部分才能公开。一旦暴露,您需要记住,调用参数或语义的任何更改可能会破坏向后兼容性。作为一般规则,如果您不确定是否公开特定功能,那么您可能不应该。
那么,我们如何定义更多的功能,而不会混淆命名空间,而不会暴露实现?这是关闭的工作。为了演示,我们将在我们的插件中添加另一个函数叫做“debug”。调试功能将所选元素的数量记录到控制台。要创建一个闭包,我们将整个插件定义包装在一个函数中(如jQuery创作指南中所详述的)。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// Create closure. (function( $ ) { // Plugin definition. $.fn.hilight = function( options ) { debug( this ); // ... }; // Private function for debugging. function debug( obj ) { if ( window.console && window.console.log ) { window.console.log( "hilight selection count: " + obj.length ); } }; // ... // End of closure. })( jQuery ); |
我们的“调试”方法无法从关闭的外部访问,因此对我们的实现是私有的。
链接Bob和Sue
假设鲍勃创建了一个邪恶的新画廊插件(称为“超级画廊”),其中包含一幅图像列表,并使其可导航。鲍勃抛出了一些动画,使它更有趣。他试图使插件尽可能的自定义,最终结果是这样的:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
三十
|
jQuery.fn.superGallery = function( options ) { // Bob's default settings: var defaults = { textColor: "#000", backgroundColor: "#fff", fontSize: "1em", delay: "quite long", getTextFromTitle: true, getTextFromRel: false, getTextFromAlt: false, animateWidth: true, animateOpacity: true, animateHeight: true, animationDuration: 500, clickImgToGoToNext: true, clickImgToGoToLast: false, nextButtonText: "next", previousButtonText: "previous", nextButtonTextColor: "red", previousButtonTextColor: "red" }; var settings = $.extend( {}, defaults, options ); return this.each(function() { // Plugin code would go here... }); }; |
第一件事可能是你的头脑(好的,也许不是第一个),这个插件必须有多大,以适应这样一个级别的定制。插件,如果不是虚构的,可能会比必要的大得多。只有这么多千字节的人会愿意花费!
现在,我们的朋友鲍勃认为这一切都很好; 事实上,他对插件及其定制级别印象深刻。他认为,所有的选择都可以使用更为通用的解决方案,可以在许多不同的情况下使用。
苏,我们的另一个朋友,决定使用这个新的插件。她已经设置了所有的选项,现在有一个工作的方案坐在她面前。只有五分钟后,玩插件后,如果每张图片的宽度都以较慢的速度动画,她意识到画廊会更好看。她匆忙搜索Bob的文档,但没有发现animateWidthDuration选项!
链接你看到问题吗?
这不是真的关于您的插件有多少选项; 但它有什么选择!
鲍勃已经超过了顶峰。他提供的定制水平虽然可能高,但实际上相当低,特别是考虑到使用此插件时可能需要控制的所有可能的事情。鲍勃犯了错误提供了很多可笑的具体选择,使他的插件更难以自定义!
链接更好的模型
所以很明显:鲍勃需要一个新的定制模式,一个不放弃控制或抽出必要细节的模型。
鲍勃如此吸引这个高级简单性的原因是,jQuery框架非常适合于这种观念。提供一个previousButtonTextColor选项是好的和简单的,但让我们面对它,绝大多数的插件用户将要更多的控制!
以下是一些提示,可帮助您为插件创建更好的可定制选项:
链接不要创建特定于插件的语法
使用您的插件的开发人员不应该学习一种新的语言或术语,只是为了完成这项工作。
鲍勃认为他提供最大的定制与他的延迟选项(看上面)。他做的这样,他的插件可以指定四个不同的延迟,“很短”,“很短”,“相当长”或“很长”:
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var delayDuration = 0; switch ( settings.delay ) { case "very short": delayDuration = 100; break; case "quite short": delayDuration = 200; break; case "quite long": delayDuration = 300; break; case "very long": delayDuration = 400; break; default: delayDuration = 200; } |
这不仅限制了人们的控制水平,而且占用了相当多的空间。十二行代码只是为了定义延迟时间有点多,你不觉得吗?构建此选项的更好方法是让插件用户以数字的形式指定时间(以毫秒为单位),以便不需要处理该选项。
这里的关键不在于通过抽象来减少控制的程度。您的抽象,无论如何,可以像您想要的一样简单,但确保使用您的插件的人仍然会有这么多追捧的低级控制!(低级我的意思是非抽象的)
链接充分控制元素
如果您的插件创建要在DOM中使用的元素,那么为插件用户提供一些访问这些元素的方法是个好主意。有时这意味着给予某些元素ID或类。但请注意,您的插件不应该在内部依赖这些钩子:
执行不良
1
2
3
4
|
// Plugin code $( "<div class='gallery-wrapper' />" ).appendTo( "body" ); $( ".gallery-wrapper" ).append( "..." ); |
为了允许用户访问甚至操纵该信息,您可以将其存储在包含插件设置的变量中。以前的代码的更好的实现如下所示:
1
2
3
4
五
6
7
|
// Retain an internal reference: var wrapper = $( "<div />" ) .attr( settings.wrapperAttrs ) .appendTo( settings.container ); // Easy to reference later... wrapper.append( "..." ); |
请注意,我们创建了一个引用包装器的引用,并且我们还调用该.attr()
方法来向元素添加任何指定的属性。所以在我们的设置中可能会像这样处理:
1
2
3
4
五
6
7
8
9
10
|
var defaults = { wrapperAttrs : { class: "gallery-wrapper" }, // ... rest of settings ... }; // We can use the extend method to merge options/settings as usual: // But with the added first parameter of TRUE to signify a DEEP COPY: var settings = $.extend( true, {}, defaults, options ); |
在$ .extend()方法现在将通过所有嵌套对象递归给我们设定值和通过选择两者的合并版本,给人传递的选项优先。
插件用户现在有权力指定该包装器元素的任何属性,因此如果它们需要任何CSS样式的钩子,那么他们可以很容易地添加一个类或更改ID的名称,而无需去挖掘插件源码。
可以使用相同的模型来让用户定义CSS样式:
1
2
3
4
五
6
7
8
9
10
|
var defaults = { wrapperCSS: {}, // ... rest of settings ... }; // Later on in the plugin where we define the wrapper: var wrapper = $( "<div />" ) .attr( settings.wrapperAttrs ) .css( settings.wrapperCSS ) // ** Set CSS! .appendTo( settings.container ); |
您的插件可能有一个关联的样式表,开发人员可以添加CSS样式。即使在这种情况下,最好提供一些方便的方法来设置JavaScript中的样式,而无需使用选择器来获取元素。
链接提供回调功能
什么是回调? - 回调本质上是一个稍后调用的函数,通常由一个事件触发。它作为参数传递,通常是组件的发起调用,在这种情况下是一个jQuery插件。
如果您的插件由事件驱动,那么为每个事件提供回调功能可能是一个好主意。此外,您可以创建自己的自定义事件,然后为其提供回调。在这个gallery插件中添加一个“onImageShow”回调可能是有意义的。
1
2
3
4
五
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var defaults = { // We define an empty anonymous function so that // we don't need to check its existence before calling it. onImageShow : function() {}, // ... rest of settings ... }; // Later on in the plugin: nextButton.on( "click", showNextImage ); function showNextImage() { // Returns reference to the next image node var image = getNextImage(); // Stuff to show the image here... // Here's the callback: settings.onImageShow.call( image ); } |
而不是通过传统方式(加括号)启动回调,我们在上下文中调用它将image
是对图像节点的引用。这意味着您可以通过this
回调中的关键字访问实际的图像节点:
1
2
3
4
五
6
7
|
$( "ul.imgs li" ).superGallery({ onImageShow: function() { $( this ).after( "<span>" + $( this ).attr( "longdesc" ) + "</span>" ); }, // ... other options ... }); |
同样,您可以添加一个“onImageHide”回调函数和其他许多回调函数。回调点是给插件用户一个简单的方法来添加额外的功能,而不需要在源代码中挖掘。
链接记住,这是一个妥协
您的插件无法在每种情况下工作。同样地,如果您提供的控制方法不是很少或很少,则不会非常有用。所以,请记住,这将是一个妥协。您必须始终考虑的三件事情是:
- 灵活性:您的插件可以处理多少情况?
- 大小:插件的大小是否对应于其功能级别?也许你使用一个非常基本的工具提示插件,如果它是20k的大小?- 可能不会!
- 性能:您的插件是否以任何方式大量处理选项?这是否影响速度?为最终用户带来的开销是否值得?