深入学习jquery源码之插件机制(二)

时间:2023-02-23 21:56:04


高级插件概念
链接提供对默认插件设置的公共访问
我们可以而且应该对上面的代码做出的改进并公开默认的插件设置。 这很重要,因为它使插件用户很容易用最少的代码覆盖/自定义插件。 这就是我们开始利用函数对象的地方。

// 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"
};

现在用户可以在他们的脚本中包含这样的行:

// This needs only be called once and does not
// have to be called from within a "ready" block
$.fn.hilight.defaults.foreground = "blue";

现在我们可以像这样调用插件方法,它将使用蓝色前景色:

$( "#myDiv" ).hilight();

如您所见,我们允许用户编写一行代码来更改插件的默认前景色。 用户仍然可以在需要时有选择地覆盖这个新的默认值:

// 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文本。 我们的插件现在看起来像这样,使用hilight函数下面定义的格式方法的默认实现:

// 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>";
};

我们可以轻松地支持options对象上的另一个属性,该属性允许提供回调函数来覆盖默认格式。这是支持自定义插件的另一种很好的方式。这里显示的技术通过实际暴露格式函数使其可以重新定义,从而更进一步。使用这种技术,其他人可以发送他们自己的插件自定义覆盖 - 换句话说,这意味着其他人可以为您的插件编写插件。

考虑到我们在本文中构建的简单示例插件,您可能想知道何时这将有用。一个真实的例子是Cycle Plugin。 Cycle Plugin是一个幻灯片插件,它支持许多内置的过渡效果 - 滚动,幻灯片,淡入淡出等。但实际上,没有办法定义一个人可能希望应用于幻灯片过渡的每种类型的效果。这就是这种可扩展性有用的地方。 Cycle Plugin公开了一个“过渡”对象,用户可以在其中添加自己的自定义过渡定义。它在插件中定义如下:

$.fn.cycle.transitions = {

// ...

};

这种技术使其他人可以定义和发送插件到Cycle Plugin的转换定义。

保持私人功能
暴露部分插件被覆盖的技术可能非常强大。 但是,您需要仔细考虑要公开的实现的哪些部分。 一旦暴露,您需要记住,对调用参数或语义的任何更改都可能会破坏向后兼容性。 作为一般规则,如果您不确定是否要公开特定功能,那么您可能不应该这样做。

那么我们如何定义更多的函数而不会混淆命名空间并且不暴露实现呢? 这是关闭的工作。 为了演示,我们将在插件中添加另一个名为“debug”的函数。 调试功能会将所选元素的数量记录到控制台。 要创建闭包,我们将整个插件定义包装在一个函数中。

// 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 );

我们的“调试”方法无法从闭包外部访问,因此对我们的实现是私有的。

新图库插件(称为“superGallery”),该插件获取图像列表并使其可导航。 想要投入一些动画使其更有趣。 试图让插件尽可能地自定义,最终得到了类似这样的东西:

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...
});

};

插件,如果不出意外,可能会比必要的大得多。认为所有选项都可以提供更通用的解决方案,可以在许多不同的情况下使用。

决定使用这个新插件。已经设置了所需的所有选项,现在有一个工作解决方案出现在面前。在使用插件后意识到如果每个图像的宽度都以较慢的速度动画,那么画廊看起来会更好。急忙搜索文档,但没有找到animateWidthDuration选项!

这并不是关于你的插件有多少选项;但它有什么选择!

一个更好的模型
所以很明显需要一个新的定制模型,一个不放弃控制或抽象必要细节的模型。

以下是一些技巧,可以帮助您为插件创建更好的可自定义选项集:

不要创建特定于插件的语法
使用您的插件的开发人员不应该只是为了完成工作而学习新的语言或术语。

提供了最大程度的定制和延迟选项。 这个插件你可以指定四种不同的延迟,“很短”,“非常短”,“很长”或“非常长”:

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或类。 但请注意,您的插件不应该在内部依赖这些元素:

一个糟糕的实现:

//插件代码
$(“<div class ='gallery-wrapper'/>”).appendTo(“body”);

$(“.gallery-wrapper”).append(“...”);

要允许用户访问甚至操作该信息,您可以将其存储在包含插件设置的变量中。 以下代码的更好实现如下所示:

//保留内部参考:
var wrapper = $(“<div />”)
.attr(settings.wrapperAttrs)
.appendTo(settings.container);

//以后容易参考......
wrapper.append(“...”);

请注意,我们已经创建了对注入包装器的引用,我们还调用.attr()方法将任何指定的属性添加到元素中。 因此,在我们的设置中,它可能会像这样处理:

var defaults = {
wrapperAttrs : {
class: "gallery-wrapper"
},
// ... rest of settings ...
};

//我们可以像往常一样使用extend方法合并选项/设置:
//但是添加了第一个参数TRUE来表示DEEP COPY:
var settings = $.extend( true, {}, defaults, options );

jQuery.extend(object)

扩展jQuery对象本身。

用来在jQuery命名空间上增加新函数。

jQuery.extend({
min: function(a, b) { return a < b ? a : b; },
max: function(a, b) { return a > b ? a : b; }
});
jQuery.min(2,3); // => 2
jQuery.max(4,5); // => 5

$ .extend()方法现在将遍历所有嵌套对象,为我们提供默认值和传递选项的合并版本,从而为传递的选项提供优先权。

插件用户现在可以指定该包装元素的任何属性,因此如果他们需要有任何CSS样式的钩子,那么他们可以很容易地添加一个类或更改ID的名称,而不必去挖掘 插件源码。

可以使用相同的模型让用户定义CSS样式:

var defaults = {
wrapperCSS: {},
// ... rest of settings ...
};

//稍后在我们定义包装器的插件中:
var wrapper = $( "<div />" )
.attr( settings.wrapperAttrs )
.css( settings.wrapperCSS ) // ** Set CSS!
.appendTo( settings.container );

您的插件可能有一个关联的样式表,开发人员可以在其中添加CSS样式。 即使在这种情况下,提供一些在JavaScript中设置样式的便捷方式也是一个好主意,而无需使用选择器来获取元素。

提供回调功能
什么是回调?  - 回调本质上是一个稍后调用的函数,通常由事件触发。 它作为参数传递,通常用于组件的初始调用,在本例中是一个jQuery插件。

如果您的插件是由事件驱动的,那么为每个事件提供回调功能可能是个好主意。 此外,您可以创建自己的自定义事件,然后为这些事件提供回调。 在这个库插件中添加“onImageShow”回调可能是有意义的。

var defaults = {

//我们定义一个空的匿名函数
//在调用之前我们不需要检查它的存在。
onImageShow : function() {},

// ... rest of settings ...

};

//稍后在插件中:

nextButton.on( "click", showNextImage );

function showNextImage() {

//返回对下一个图像节点的引用
var image = getNextImage();

//这里显示图片的东西......

//这是回调:
settings.onImageShow.call( image );
}

我们不是通过传统方式启动回调(添加括号),而是在图像上下文中调用它,这将是对图像节点的引用。 这意味着您可以通过回调中的this关键字访问实际的图像节点:

$( "ul.imgs li" ).superGallery({
onImageShow: function() {
$( this ).after( "<span>" + $( this ).attr( "longdesc" ) + "</span>" );
},

// ... other options ...
});

类似地,您可以添加“onImageHide”回调和许多其他回调。 回调的目的是为插件用户提供一种简单的方法来添加其他功能,而无需在源代码中进行挖掘。

你的插件无法在任何情况下都能正常工作。 同样,如果你不提供或只提供很少的控制方法,它就不会非常有用。 所以,请记住,它总是会妥协。 您必须始终考虑的三件事是:

灵活性:您的插件能够处理多少种情况?
大小:插件的大小是否与其功能级别相对应?即 如果它是20k大小,你会使用一个非常基本的工具提示插件吗? - 可能不是!
性能:您的插件是否以任何方式大量处理选项? 这会影响速度吗? 最终用户的开销是否值得?