jQuery插件开发模式(上)

A Plugin Development Pattern

read 107 comments

I've been developing jQuery plugins for quite a while now, and I've become rather comfortable with a particular style of plugin development for my scripts. This article is meant to share the pattern that I've found especially useful for plugin authoring. It assumes you already have an understanding of plugin development for jQuery; if you're a novice plugin author, please review thejQuery Authoring Guidelines first.

There are a few requirements that I feel this pattern handles nicely:

  1. Claim only a single name in the jQuery namespace
  2. Accept an options argument to control plugin behavior
  3. Provide public access to default plugin settings
  4. Provide public access to secondary functions (as applicable)
  5. Keep private functions private
  6. Support the Metadata Plugin

I'll cover these requirements one by one, and as we work through them we'll build a simple plugin which highlights text.

Claim only a single name in the jQuery namespace

This implies a single-plugin script. If your script contains multiple plugins, or complementary plugins (like $.fn.doSomething() and $.fn.undoSomething()) then you'll claim multiple names are required. But in general when authoring a plugin, strive to use only a single name to hold all of its implementation details.

In our example plugin we will claim the name "hilight".

JavaScript:
  1. // plugin definition
  2. $. fn. hilight = function ( ) {
  3.   // Our plugin implementation code goes here.
  4. };

And our plugin can be invoked like this:

JavaScript:
  1. $ ( '#myDiv' ). hilight ( );

But what if we need to break up our implementation into more than one function? There are many reasons to do so: the design may require it; it may result in a simpler or more readable implementation; and it may yield betterOO semantics.

It's really quite trivial to break up the implementation into multiple functions without adding noise to the namespace. We do this by recognizing, and taking advantage of, the fact thatfunctions are first-class objects in JavaScript. Like any other object, functions can be assigned properties. Since we have already claimed the "hilight" name in the jQuery prototype object, any other properties or functions that we need to expose can be declared as properties on our "hilight" function. More on this later.

Accept an options argument to control plugin behavior

Let's add support to our hilight plugin for specifying the foreground and background colors to use. We should allow options like these to be passed as an options object to the plugin function. For example:

JavaScript:
  1. // plugin definition
  2. $. fn. hilight = function (options ) {
  3.   var defaults = {
  4.     foreground : 'red' ,
  5.     background : 'yellow'
  6.   };
  7.   // Extend our default options with those provided.
  8.   var opts = $.extend (defaults , options );
  9.   // Our plugin implementation code goes here.
  10. };

Now our plugin can be invoked like this:

JavaScript:
  1. $ ( '#myDiv' ). hilight ( {
  2.   foreground : 'blue'
  3. } );

Provide public access to default plugin settings

An improvement we can, and should, make to the code above is to expose the default plugin settings. This is important because it makes it very easy for plugin users to override/customize the plugin with minimal code. And this is where we begin to take advantage of the function object.

JavaScript:
  1. // plugin definition
  2. $. fn. hilight = function (options ) {
  3.   // Extend our default options with those provided.
  4.   // Note that the first arg to extend is an empty object -
  5.   // this is to keep from overriding our "defaults" object.
  6.   var opts = $.extend ( { } , $. fn. hilight. defaults , options );
  7.   // Our plugin implementation code goes here.
  8. };
  9. // plugin defaults - added as a property on our plugin function
  10. $. fn. hilight. defaults = {
  11.   foreground : 'red' ,
  12.   background : 'yellow'
  13. };

Now users can include a line like this in their scripts:

JavaScript:
  1. // this need only be called once and does not
  2. // have to be called from within a 'ready' block
  3. $. fn. hilight. defaults. foreground = 'blue';

And now we can call the plugin method like this and it will use a blue foreground color:

JavaScript:
  1. $ ( '#myDiv' ). hilight ( );

As you can see, we've allowed the user to write a single line of code to alter the default foreground color of the plugin. And users can still selectively override this new default value when they want:

JavaScript:
  1. // override plugin default foreground color
  2. $. fn. hilight. defaults. foreground = 'blue';
  3. // ...
  4. // invoke plugin using new defaults
  5. $ ( '.hilightDiv' ). hilight ( );
  6. // ...
  7. // override default by passing options to plugin method
  8. $ ( '#green' ). hilight ( {
  9.   foreground : 'green'
  10. } );

Provide public access to secondary functions as applicable

This item goes hand-in-hand with the previous item and is an interesting way to extend your plugin (and to let others extend your plugin). For example, the implementation of our plugin may define a function called "format" which formats the hilight text. Our plugin may now look like this, with the default implementation of the format method defined below the hilight function.

JavaScript:
  1. // plugin definition
  2. $. fn. hilight = function (options ) {
  3.   // iterate and reformat each matched element
  4.   return this. each ( function ( ) {
  5.     var $this = $ ( this );
  6.     // ...
  7.     var markup = $this. html ( );
  8.     // call our format function
  9.     markup = $. fn. hilight. format (markup );
  10.     $this. html (markup );
  11.   } );
  12. };
  13. // define our format function
  14. $. fn. hilight. format = function (txt ) { '
  15.  return ' <strong> ' + txt + ' </strong> ';
  16. };

We could have just as easily supported another property on the options object that allowed a callback function to be provided to override the default formatting. That's another excellent way to support customization of your plugin. The technique shown here takes this a step further by actually exposing the format function so that it can be redefined. With this technique it would be possible for others to ship their own custom overrides of your plugin נin other words, it means others can write plugins for your plugin.

Considering the trivial example plugin we're building in this article, you may be wondering when this would ever be useful. One real-world example is theCycle Plugin. The Cycle Plugin is a slideshow plugin which supports a number of built-in transition effects נscroll, slide, fade, etc. But realistically, there is no way to define every single type of effect that one might wish to apply to a slide transition. And that's where this type of extensibility is useful. The Cycle Plugin exposes a "transitions" object to which users can add their own custom transition definitions. It's defined in the plugin like this:

JavaScript:
  1. $. fn. cycle. transitions = {
  2.   // ...
  3. };

This technique makes it possible for others to define and ship transition definitions that plug-in to the Cycle Plugin.

Keep private functions private

The technique of exposing part of your plugin to be overridden can be very powerful. But you need to think carefully about what parts of your implementation to expose. Once it's exposed, you need to keep in mind that any changes to the calling arguments or semantics may break backward compatibility. As a general rule, if you're not sure whether to expose a particular function, then you probably shouldn't.

So how then do we define more functions without cluttering the namespace and without exposing the implementation? This is a job forclosures. To demonstrate, we'll add another function to our plugin called "debug". The debug function will log the number of selected elements to theFirebug console. To create a closure, we wrap the entire plugin definition in a function (as detailed in thejQuery Authoring Guidelines).

JavaScript:
  1. // create closure
  2. ( function ($ ) {
  3.   // plugin definition
  4.   $. fn. hilight = function (options ) {
  5.     debug ( this );
  6.     // ...
  7.   };
  8.   // private function for debugging
  9.   function debug ($obj ) {
  10.     if (window. console && window. console. log )
  11.       window. console. log ( 'hilight selection count: ' + $obj. size ( ) );
  12.   };
  13.   //  ...
  14. // end of closure
  15. } ) (jQuery );

Our "debug" method cannot be accessed from outside of the closure and thus is private to our implementation.

Support the Metadata Plugin
Depending on the type of plugin you're writing, adding support for the Metadata Plugin can make it even more powerful. Personally, I love the Metadata Plugin because it lets you use unobtrusive markup to override plugin options (which is particularly useful when creating demos and examples). And supporting it is very simple!

Update: This bit was optimized per suggestion in the comments.

JavaScript:
  1. // plugin definition
  2. $. fn. hilight = function (options ) {
  3.   // ...
  4.   // build main options before element iteration
  5.   var opts = $.extend ( { } , $. fn. hilight. defaults , options );
  6.   return this. each ( function ( ) {
  7.     var $this = $ ( this );
  8.     // build element specific options
  9.     var o = $. meta ? $.extend ( { } , opts , $this. data ( ) ) : opts;
  10.     //...

This changed line does a couple of things:

  • it tests to see if the Metadata Plugin is installed
  • if it is installed, it extends our options object with the extracted metadata.

This line is added as the last argument to jQuery.extend so it will override any other option settings. Now we can drive behavior from the markup if we choose:

<!--  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>

And now we can hilight each of these divs uniquely using a single line of script:

JavaScript:
  1. $ ( '.hilight' ). hilight ( );

Putting it All Together

Below is the completed code for our example:

JavaScript:
  1. //
  2. // create closure
  3. //
  4. ( function ($ ) {
  5.   //
  6.   // plugin definition
  7.   //
  8.   $. fn. hilight = function (options ) {
  9.     debug ( this );
  10.     // build main options before element iteration
  11.     var opts = $.extend ( { } , $. fn. hilight. defaults , options );
  12.     // iterate and reformat each matched element
  13.     return this. each ( function ( ) {
  14.       $this = $ ( this );
  15.       // build element specific options
  16.       var o = $. meta ? $.extend ( { } , opts , $this. data ( ) ) : opts;
  17.       // update element styles
  18.       $this. css ( {
  19.         backgroundColor : o. background ,
  20.         color : o. foreground
  21.       } );
  22.       var markup = $this. html ( );
  23.       // call our format function
  24.       markup = $. fn. hilight. format (markup );
  25.       $this. html (markup );
  26.     } );
  27.   };
  28.   //
  29.   // private function for debugging
  30.   //
  31.   function debug ($obj ) {
  32.     if (window. console && window. console. log )
  33.       window. console. log ( 'hilight selection count: ' + $obj. size ( ) );
  34.   };
  35.   //
  36.   // define and expose our format function
  37.   //
  38.   $. fn. hilight. format = function (txt ) {
  39.     return '<strong>' + txt + '</strong>';
  40.   };
  41.   //
  42.   // plugin defaults
  43.   //
  44.   $. fn. hilight. defaults = {
  45.     foreground : 'red' ,
  46.     background : 'yellow'
  47.   };
  48. //
  49. // end of closure
  50. //
  51. } ) (jQuery );

This design pattern has enabled me to create powerful, consistently crafted plugins. I hope it helps you to do the same.

=====================================================================================================

jQuery 插件通常分两类。

  1. 基于选择器的插件(支持链式操作)
  2. 不基于选择器的插件(不支持链式操作)

前段时间简单学习了 jQuery 插件开发,开发了两个简单的插件,在此对两种插件的开发模式做简要总结。

基于选择器的插件

通常开发模式如下:

(function($, window, undefined) {
	$.fn.PluginName = function(opts) {
		var defaults = {
			// 插件自定义选项的默认值
		};

		// 以用户的自定义选项覆盖默认选项
		var options = $.expend(defaults, opts || {});

		return this.each(function() { // 让插件支持链式操作
			// 在这里编写插件功能代码
		});
	};
})(jQuery, window);

首先,创建一个匿名的自执行函数,形参为 & windowundefined,实参为jQuerywindow

嗯?为什么没有为 undefined 对应地传入一个实参呢?这是一个小技巧,考虑到undefined 这个变量名可能在其它地方的 JavaScript 代码赋过值,失去了它真正的意义,所以这里干脆不传入这个参数,以确保它在那个匿名自执行函数中是真正的undefined

jQuery 传入后对应为 $,这样可以保证插件内调用的$ 一定是 jQuery 而非 Prototype 之类的库。

此类插件的调用方式一般为 $(selector).PluginName(); 这种形式。

此类具体示例可参考 https://github.com/libuchao/KTwitter

不基于选择器的插件

由于此类插件不依赖于选择器,所以也无链式操作一说。一般开发模式如下:

(function($, window, undefined) {
	$.PluginName = function(opts) {
		var defaults = {
			// 插件自定义选项的默认值
		};

		// 以用户的自定义选项覆盖默认选项
		var options = $.expend(defaults, opts || {});

		// 在这里编写插件功能代码
	};
})(jQuery, window);

此类插件的调用形式一般为 $(selector).PluginName(); 这种形式。

此类插件具体示例可参考 https://github.com/libuchao/KBox

其它事项

如何让他人乐意用你的插件?

  1. 其实不管插件是给自己用的,还是个其他人用的,代码都要尽量整洁规范,如果能提供说明文档则更好。
  2. 对于用户方面,尽量为用户提供灵活的自定义选项,如能配以演示 Demo 则更好。
  3. 要在各主流浏览器下进行测试,并对支持情况予以说明,如 Supports IE6+, FireFox3+, Safari5+, Chrome9+ …
  4. 对 jQuery 各版本支持情况要予以说明,如 Requires jQuery v1.3.2 or later …
  5. 如果插件代码体积比较大的话,尽可能同时提供带有注释的 Dev 版本和不带注释压缩过的 Min 版本(借助于 JavaScript 代码压缩工具)。
  6. 最后,测试测试再测试,保证质量。

想学习插件开发?请参考这篇文档 http://docs.jquery.com/Plugins/Authoring

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值