插件高级概念
允许外界访问插件默认配置
我们可以也应该对插件做出的一项改进是暴露插件的默认配置。这么做十分有必要因为如此以来便允许插件的使用者方便地用最短的代码覆盖插件的默认配置。这也是我们得以充分利用函数对象的开端。// 插件定义
$.fn.hilight = function( options ) {
// 根据提供的配置参数与默认配置合并
// 注意合并函数的第一个参数是一个空对象 —— 这是为了防止“默认配置”对象被修改
var opts = $.extend( {}, $.fn.hilight.defaults, options );
// 接下来是插件实现代码
};
// 插件默认配置 —— 作为属性被添加到插件中
$.fn.hilight.defaults = {
foreground: "red",
background: "yellow"
};
于是插件的用户可以在他们的脚本中添加这样一行代码:
// 仅需执行一次而且不需要非得在ready函数块内执行。
$.fn.hilight.defaults.foreground = "blue";
然后我们可以这样调用插件改变元素的前景色。
$( "#myDiv" ).higlight();
如你所见,我们允许用户编写一行代码来替换插件定义的默认前景色。而且只要用户需要的话,他们还可以继续有选择的覆盖这些默认值:
// 覆盖插件的默认前景色
$.fn.hilight.defaults.foreground = "blue";
// ...
// 覆盖插件的默认前景色
$.fn.hilight.defaults.foreground = "blue";
// ...
// 使用新的默认配置激活插件
$( ".hilightDiv" ).hilight();
// ...
// 通过传递配置参数来覆盖默认值
$( "#green" ).hilight({
foreground: "green"
});
尽可能允许外界访问二级函数
此做法与稍前的做法相辅相成而且是一种有趣的方法来拓展你的插件(同时允许别人拓展你的插件)。比如,在我们的插件的实现代码中可能定义了一个名为“format”的函数来格式化高亮文本。我们的插件看起来就像下面这样,在高亮函数下面实现了默认的格式化函数。// 定义插件
$.fn.hilight = function( options ) {
// 迭代并重新格式化每一个匹配的元素
return this.each(function() {
var $this = $( this );
// ...
var markup = $this.html();
// 调用格式化函数
markup = $.fn.hilight.format( markup );
$this.html( markup );
});
};
// 定义格式化函数
$.fn.hilight.format = function( txt ) {
return "" + txt + "";
};
我们可以给options对象添加一个属性来接受一个回调函数让它覆盖默认的格式化函数,这是另一种自定义插件的天才想法。这种让暴露格式化函数使之可以被重写的技巧让插件编写更进一步。采用这种技巧允许别人采用自己的代码来重写你的插件——换句话说,意味着别人可以为你的插件编写插件。
免去本文章里创建的这个杂七杂八的插件例子不说,你或许疑惑这些技巧什么时候才能派上用场。一个真实的案例是Cycle 插件。Cycle 插件是一个支持数种内置转换特效——翻滚、滑动、淡化等等的幻灯片轮播插件。然而现实情况是,在幻灯片转换特效中不可能定义所有的特效类型。这种情况下上面提及的拓展性技巧便大有用处。Cycle 插件暴露了一个“transitions”对象给用户增添自己的转换特效代码。它的定义如下:
$.fn.cycle.transitions = {
// ...
};
这个技巧允许别人定义并添加转换代码到Cycle 插件当中。
保持私有函数的私有性
部分暴露你的插件以便被重写的技巧相当强大。但请慎重考虑你的实现代码中哪些部分要被暴露。一旦被暴露出去,要在头脑中时刻牢记任何对调用参数或语义的改动都可能破坏后向兼容性。一条通用法则是,如果你不确定是否应该暴露某特定函数,那干脆别这么做。所以我们该如何在不破坏命名空间和不暴露实现的前提下定义更多的函数呢?这边是闭包函数的工作了。为了演示,我们将增加一个名为“debug”的函数到我们的插件中。debug函数会在Firebug控制台总输出选中元素的个数。为了创建闭包,我们把整个插件定义包裹在一个函数中(在jQuery开发指导中获取更多细节)。
// 创建闭包
(function( $ ) {
// 插件定义
$.fn.hilight = function( options ) {
debug( this );
// ...
};
// 私有调试函数
function debug( $obj ) {
if ( window.console && window.console.log ) {
window.console.log( "选中的高亮元素个数: " + $obj.size() );
}
};
// ...
// 闭包结束
})( jQuery );
我们的“debug”函数对闭包外部来说是不可访问的,因此对于我们的实现代码来说是私有成员。
鲍勃和苏
比如说鲍勃编写了一个操蛋的轮播插件(叫做“superGallery”)可以接收一系列图片并让它们滚动。鲍勃编写了一些动画效果让它们看上去更有趣。他尽力让插件尽可能可定制化,然后做出了这么个玩意儿。jQuery.fn.superGallery = function( options ) {
// 鲍勃的默认设置:
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() {
// 插件代码走起
});
};
你脑子里划过的第一个念头(好吧,或许不是第一个)便是这个插件将来得有多大才能承受这个级别的可定制化。这个插件,不开玩笑的话,将会比实际需要的大出很多。谁会乐意多走这么多kb的流量!
现在,我们的小伙伴鲍勃觉得这样很不错;他对如此高端的可定制化引以为豪。他相信所有的这些选项都是为了适应不同的方案,适用于不同的场合。
苏,我们的另一个小伙伴,刚决定采用这个新插件。她配置好了所有要求的参数而且还有一个项目方案摆在她面前。仅仅在五分钟之后,在和这个插件熟悉了一会儿之后,她想要是每张照片的宽度都是动画过渡的话这个轮播效果看上去会更加平滑。她匆忙地翻阅鲍勃的文档结果却找不到animateWidthDuration选项!
发现问题所在了么?
问题完全不在于你的插件有多少个选项,而在于应该有什么选项!
鲍勃做的有些过头了。他提供的个性化程度看上去可能很高,实际上却很低,尤其是当考虑到照顾某些人在各方面的控制要求的时候。鲍勃因提供了过多无意义的选项而失误了,导致他的插件反而更加难以被可定制。
更好的模型
显而易见:鲍勃需要一个新的定制化模型,一个不丢弃控制权或者不偏离必要细节的模型。
鲍勃如此陷入他的高级简化误区的原因在于jQuery框架迎合了这种心态。提供一个previousButtonTextColor选项简洁漂亮,但面对现实吧,大多数插件用户想要更多的控制。
下面是一些创建更好的个性化选项的提示:
不要使用针对插件的语法
开发者为了完成任务不应该单独针对你学习一门新的语言或术语。
鲍勃想给他的dely选项(看上面)提供最大限度的可定制化。他于是便这么做了使得用它的插件你可以定义四个延迟等级,“very short”,“quite short”,”quite long“,”very long“:
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;
}
这不仅限制了用户的控制权,也占用了更多的空间。12行代码仅仅是为了定义延迟时间未免太过度了,你觉得呢?一种更好的组织选项的实践方法是让用户使用数字指定时间(基于毫秒),这样一来便不需要对该参数进行解析判断。
开放元素的控制权
如果你的插件创建了在DOM中使用的元素,把它们提供给用户们访问会更加好。有的时候意味着给出指定元素们的ID或者class。但请注意不要在你的插件内部依赖这些元素。一份糟糕的实现:
// 插件代码
$( "").appendTo( "body" );
$( "#the-gallery-wrapper" ).append( "..." );
// 保留内部的引用
var $wrapper = $( "" ) .attr( settings.wrapperAttrs ) .appendTo( settings.container );
$wrapper.append( "..." ); // 方便之后引用...
注意我们保存了一份对传入的包裹元素的引用并且同时调用了.attr()方法给元素添加指定的属性。所以我们的配置信息应该是这样被处理的:
var defaults = {
wrapperAttrs : {
id: "gallery-wrapper"
},
// ... 余下的配置 ...
};
// 我们可以像以往一样用extend方法合并 options/settings
// 但给第一个参数传递true值来指明深拷贝(注:递归拷贝options对象包含的对象)
var settings = $.extend( true, {}, defaults, options );
$.extend()方法现在会递归的遍历所有内层对象然后返回一份默认配置和传入配置的合并结果。
插件用户现在有能力给包裹元素指定任意属性,因此如果他们需要一个可以修改css样式、增添class、改变ID值的全局钩子那么他们将不必需深挖整个插件自身。
var defaults = {
wrapperCSS: {},
// ... 余下的配置 ...
};
// 紧接着我们在插件中定义wrapper的位置
var $wrapper = $( "
" ) .attr( settings.wrapperAttrs ) .css( settings.wrapperCSS ) // ** 设置CSS! .appendTo( settings.container );
你的插件插件可能包含相关样式表以给开发之提供CSS样式支持。即便在这种情况下使用JavaScript提供一些设置样式的便捷方法也是值得推荐的,不必要再去用选择器获取元素。
提供回调功能
什么是回调函数?——回调函数会在幕后被调用,通常被事件激活。它被当成参数进行传递,jQuery插件通常被用来初始化一部分组件。如果你的插件是基于事件驱动的那么最好给每个事件提供回调支持。另外,你也可以创建自己的事件然后为它们提供回调函数。在这份轮播插件中添加一个onImageShow回调函数将十分有意义。
var defaults = {
// 定义一个匿名函数因此使用前不必检查他的存在性
onImageShow : function() {},
// ... 余下的配置 ...
};
// 紧接着的插件部分
$nextButton.bind( "click", showNextImage );
function showNextImage() {
// 展示图片的工作...
// 这里是回调函数:
settings.onImageShow.call( this );
}
并非采用传统的方式(添加圆括号)来初始化回调函数,而是使用this,this将会是对图像节点的引用。这意味着你可以在回调内部使用this关键字来引用图像节点。
$( "ul.imgs li" ).superGallery({
onImageShow: function() {
$( this ).after( "" + $( this ).attr( "longdesc" ) + "" );
},
// ... 其他配置 ...
});
类似的你也可以添加一个”onImageHide“回调函数或是数不清的别的什么东西。回调函数的意义在于给插件用户提供一种添加个性函数的简单方式而不必邀挖掘整个源头。
切记,一切都只是折中方案
你的插件没有三头六臂。同样的,仅给出寥寥几个方法控制插件的行为也难以发挥用处。所以请切记,一切都只是折中方案。牢记三定律:
通用性:你的插件能解决多少中情况?
代码量:你的代码量合乎它的功能么?也就是说,你会用一个20kb的工具提示插件么?——八成不会吧!
性能:你的插件解析配置的负担重么?这是否会影响速度?过度的功能值得用户使用么?a