前几天一个工作需要,写了一个jquery的小插件,想起来这篇让我受益匪浅的老文章,翻译出来给大家看看。这是我翻译的第一篇文章,如果觉得实在太差,看不下去了,请移步原文: http://www.learningjquery.com/2007/10/a-plugin-development-pattern 。真的是好文一篇! ----------------------------------我只是分割线------------------------------------------ 我开发jQuery插件已经有一段时间了,这个过程中我形成了一个很舒服的插件开发风格。这篇文章我就介绍一下我发现的这个对插件开发很有帮助的模式。这里我假设你对jQuery插件的开发已经有一个基本的理解了。如果你还是个新手,建议你先看一下 jQuery Authoring Guidelines 这里列举一些我认为这个模式做得好的点(requirements)
在jQuery的命名空间中声明一个简单(唯一)的名字
接收一些可选的参数来控制插件的行为
提供对插件默认配置的访问方法
提供对辅助功能的访问方法
保持私有方法是私有的
支持插件元数据(the Metadata Plugin)
下面我将对上面这些点逐一解释,并且按照他们实现一个文本高亮的小插件。
这句话的隐含意思是:单一插件的脚本(a single-plugin script),假如你的脚本中包含多个插件,或者一对功能对称的插件(比如$.fn.doSomething() 和 $.fn.undoSomething() ),那么你应该声明多个名字。一般情况下。我们开发插件的时候,都力求做到能用一个简单的名字来包含/表达插件所有的功能细节。 在我们接下来的这个例子中,我们就用“hilight”这个名字。
// plugin definition $.fn.hilight = function() { // Our plugin implementation code goes here. };
然后,可以这样调用我们的插件:
$('#myDiv').hilight();
如果要将我们的实现拆成多个函数该怎么办呢?我们有足够的理由要拆成多个函数,比如:设计的需要啊;代码可读性的需要啊;为了符合OO语义的需要啊;等等。 将实现拆分成多个函数且不污染jQuery的命名空间其实挺微琐碎的一件事儿。我们借着“在JavaScript中,function就是一级对象”的特性有意识的来做到这点。和其他对象一样,function也可以有自己的属性。我们刚刚已经在jQuery的原型对象上声明了一个“hilight”。然后任何需要被暴露的属性和方法我们都可以作为hilight函数的属性声明出来。这个等会儿还会作详细解释。
我们给hilight插件加上指定特定前景色和背景色的功能。我们需要允许向插件函数传递一个配置对象来进行配置。比如这样:
// plugin definition $.fn.hilight = function(options) { var defaults = { foreground: 'red', background: 'yellow' }; // Extend our default options with those provided. var opts = $.extend(defaults, options); // Our plugin implementation code goes here. };
现在我们的插件就可以这样调用了。
$('#myDiv').hilight({ foreground: 'blue' });
我们可以(应该)将插件的默认配置用下面的方法暴露出去。这很重要,因为这可以让插件的使用者用很容易用很少的代码来覆盖/自定义插件。然后这里我们就利用到了function对象作为“一级对象”的便利特性。
// plugin definition $.fn.hilight = function(options) { // Extend our default options with those provided. // Note that the first arg 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 need 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' });
这一条和前面几条是紧密相连的,是一个扩展你的插件的有趣的方式(包括让别人来扩展你的插件)。例如,我们的实现中可能包含一个叫format的方法来对要高亮的文本进行格式化。这时我们的插件看起来是这样的,我们在hilight方法下面提供了一个format方法的默认实现。
// plugin definition $.fn.hilight = function(options) { // iterate and reformat each matched element return this.each(function() { var $this = $(this); // ... var markup = $this.html(); // call our format function markup = $.fn.hilight.format(markup); $this.html(markup); }); }; // define our format function $.fn.hilight.format = function(txt) {' return '<strong>' + txt + '</strong>'; };
我们也可以简单的在配置对象中提供一个属性来作为回调函数来达到覆盖默认的格式化。这是另一种让你的插件支持可定制化的好方法。这里展示的是进一步的将format方法暴露出去,让别人可以对他进行重新定义。通过这种技术,别人可以将他们自己对你的插件的修改提供给其他人用,换句话说,就是能让别人给你的插件写插件。 在我们这篇文章中介绍的这个小插件中,你可能认为这没什么用。有一个真实的例子是 Cycle Plugin,Cycle Plugin是一个提供了很多内置动画效果(滚动,滑动,淡入淡出)的幻灯片插件。但是事实上,他没有提供一种实现简单的幻灯片切换效果的途径。那,这里就是上面这种扩展方式非常有用的地方了。Cycle Plugin就暴露了一个叫“transitions”的对象,让用户可以自己定义幻灯片的切换效果。在他插件代码里就是这样写的:
$.fn.cycle.transitions = { // ... };
这种技术就让其他人为Cycle Plugin编写幻灯片切换效果成为可能了。
将你的插件部分功能暴露出去让他们可以被覆盖是一个很强大的技术。但是你需要想清楚,你的哪部分实现是需要暴露出去的。一旦你暴露出去了,就要时刻提醒自己:对这个函数的调用参数和语义做任何修改都可能破坏他的向后兼容性。一般的规则是,如果你不确定这个函数该不该被暴露出去,那就不要暴露出去。 那么我如何在既不污染命名空间又不将函数暴露出去的情况下添加更多的函数呢?这就是闭包(closures)干的事儿。为了演示,我们在插件里再添加一个叫“debug”的函数。debug函数将在FireBug的控制台上记录被选择的元素的数量。为了建立一个闭包,我们将整个插件的定义包起来(详见 jQuery Authoring Guidelines)。
// 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.size()); }; // ... // end of closure })(jQuery);
这样debug方法不能被闭包外面的代码所访问到,所以,它就是我们插件私有的了。
根据你写的插件的类型,提供对元数据的支持,可以让它变得更加强大。我个人喜欢它的原因是因为它可以让你用不那么扎眼的html标签就能修改插件的默认配置(这点在写示例的时候非常有用)。而且实现起来也很简单!
// plugin definition $.fn.hilight = function(options) { // ... // build main options before element iteration var opts = $.extend({}, $.fn.hilight.defaults, options); return this.each(function() { var $this = $(this); // build element specific options var o = $.meta ? $.extend({}, opts, $this.data()) : opts; //...
改变的这行做了下面这些事儿:
检查是否安装了元数据
如果安装了,用安装的元数据扩展我们的配置对象
让元数据作为jQuery.extend方法的最后一个参数,保证前面的配置一定会被它覆盖。现在,我们可以选择用标签来驱动插件的行为了。
<!-- markup --> <div class="hilight { background: 'red', foreground: 'white' }"> Have a nice day! </div> <div class="hilight { foreground: 'orange' }"> Have a nice day! </div> <div class="hilight { background: 'green' }"> Have a nice day! </div>
然后现在我们可以用一行代码就可以让每个div拥有不同的行为了。
$('.hilight').hilight();
下面是我们这个例子的完整代码:
// // create closure // (function($) { // // plugin definition // $.fn.hilight = function(options) { debug(this); // build main options before element iteration var opts = $.extend({}, $.fn.hilight.defaults, options); // iterate and reformat each matched element return this.each(function() { $this = $(this); // build element specific options var o = $.meta ? $.extend({}, opts, $this.data()) : opts; // update element styles $this.css({ backgroundColor: o.background, color: o.foreground }); var markup = $this.html(); // call our format function markup = $.fn.hilight.format(markup); $this.html(markup); }); }; // // private function for debugging // function debug($obj) { if (window.console && window.console.log) window.console.log('hilight selection count: ' + $obj.size()); }; // // define and expose our format function // $.fn.hilight.format = function(txt) { return '<strong>' + txt + '</strong>'; }; // // plugin defaults // $.fn.hilight.defaults = { foreground: 'red', background: 'yellow' }; // // end of closure // })(jQuery);
这个设计模式,让我能够开发出更加强大,结构更加一致的插件。希望它对你也有同样的效果。