jQuery 插件编写(大概翻译)
http://docs.jquery.com/Plugins/Authoring
总得来说,今年才是我开始使用JQ的一年。jQ还有一个重要的特点,那就是插件体制。写一个插件之后,可以共享给其他的人用,可以大大地节省时间。上面的链接很全面地讲了如何去编写一个jQuery插件。
开始
写一个jQuery插件,最好的方法是以插件名作为jQuery.fn的一个对象:
jQuery.fn.myPlugin = function() { // Do your awesome plugin stuff here };
但是,我却看不见那个我喜欢的美元符号了?它仍然在那里,为了防止如其他的JavaScript库之前的$符相冲突,我们还是把jQuery传入一个匿名的立刻执行函数里。这样就可以重写其他库的$符了。
(function( $ ) { $.fn.myPlugin = function() { // Do your awesome plugin stuff here }; })( jQuery );
像现在这样,我们就可以使用$来代替jQuery了。
Context(上下文)
现在我们有了自己的壳子可以编写代码了,但是在写代码之前,我有必要先讲一下context。在这个立即执行的插件函数里,这个this是一个jQuery对象,这是因为在其他的地方,this往往是指向元素对象,但是这里开发者就不必再为this包裹jQ对象了。
(function( $ ){ $.fn.myPlugin = function() { // there's no need to do $(this) because // "this" is already a jquery object // $(this) would be the same as $($('#element')); this.fadeIn('normal', function(){ //这里的this是JQ对象 // the this keyword is a DOM element //这里的this是DOM元素 }); }; })( jQuery );
$('#element').myPlugin();
基础
既然现在明白了上下文对象,我们就来写一个东西吧。
(function( $ ){ $.fn.maxHeight = function() { var max = 0; this.each(function() { max = Math.max( max, $(this).height() ); }); return max; }; })( jQuery );
var tallest = $('div').maxHeight(); // Returns the height of the tallest div
Maintaining Chainability(维护链式能力?)
前面的例子是返回DIV之间一个最高的高度,但是实际中,我们的插件更多是简单的更改集合中的元素,然后把它们传递给下一个方法。这种链式的风格也是jQ如此受欢迎的一个原因,你要返回这个this,以便可以维护链式操作。
(function( $ ){ $.fn.lockDimensions = function( type ) { return this.each(function() { var $this = $(this); if ( !type || type == 'width' ) { $this.width( $this.width() ); } if ( !type || type == 'height' ) { $this.height( $this.height() ); } }); }; })( jQuery );
$('div').lockDimensions('width').css('color', 'red');
因为这个插件返回的this是当前的作用域,它保持着这个链式操作所以接下来可以继续使用JQ的方法,例如,.CSS()。如果你的插件没有返回原有的值,你应该要返回这个this。也许,你会想,插件传入的参数中,传入了本地的作用域中,在前一个例子中,'width'字符串这个作为了插件的类型参数。(这里翻译着别扭保留原文)
Because the plugin returns the this keyword in its immediate scope, it maintains chainability and the jQuery collection can continue to be manipulated by jQuery methods, such as .css. So if your plugin doesn't return an intrinsic value, you should always return the this keyword in the immediate scope of the plugin function. Also, as you might assume, arguments you pass in your plugin invocation get passed to the immediate scope of the plugin function. So in the previous example, the string 'width' becomes the type argument for the plugin function.
Defaults and Options(默认设置和选择参数)
为了写出更复杂和更高自定义性的插件,最好的方法是使用$.extend。这样就可以避免传入一堆参数,而现在你只需要传入一个参数对象就可以了。你可以这样做:
(function( $ ){ $.fn.tooltip = function( options ) { // Create some defaults, extending them with any options that were provided var settings = $.extend( { 'location' : 'top', 'background-color' : 'blue' }, options); return this.each(function() { // Tooltip plugin code here }); }; })( jQuery );
$('div').tooltip({ 'location' : 'left' });
在这个例子中,当我们执行的插件,我们覆盖了原有的top,现在变成了left.背景色不变,所以最后的结果应该是这样子的:
{ 'location' : 'left', 'background-color' : 'blue' }
这样写的话就不用开发者定义所有的变量。
Namespacing(命名空间)
适当地使用命名空间对插件非常地重要,使用命名空间可以减少跟其他的插件冲突,可以更好地开发管理等。
Plugin Methods
Under no circumstance should a single plugin ever claim more than one namespace in the jQuery.fn
object.
(function( $ ){ $.fn.tooltip = function( options ) { // THIS }; $.fn.tooltipShow = function( ) { // IS }; $.fn.tooltipHide = function( ) { // BAD }; $.fn.tooltipUpdate = function( content ) { // !!! }; })( jQuery );
(function( $ ){ var methods = { init : function( options ) { // THIS }, show : function( ) { // IS }, hide : function( ) { // GOOD }, update : function( content ) { // !!! } }; $.fn.tooltip = function( method ) { // Method calling logic if ( methods[method] ) { return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on jQuery.tooltip' ); } }; })( jQuery ); // calls the init method $('div').tooltip(); // calls the init method $('div').tooltip({ foo : 'bar' });
// calls the hide method $('div').tooltip('hide');
// calls the update method $('div').tooltip('update', 'This is the new tooltip content!');
This type of plugin architecture allows you to encapsulate all of your methods in the plugin's parent closure, and call them by first passing the string name of the method, and then passing any additional parameters you might need for that method. This type of method encapsulation and architecture is a standard in the jQuery plugin community and it used by countless plugins, including the plugins and widgets in jQueryUI.
Events
A lesser known feature of the bind method is that is allows for namespacing of bound events. If your plugin binds an event, its a good practice to namespace it. This way, if you need to unbind it later, you can do so without interfering with other events that might have been bound to the same type of event. You can namespace your events by appending “.” to the type of event you're binding.
(function( $ ){ var methods = { init : function( options ) { return this.each(function(){ $(window).bind('resize.tooltip', methods.reposition); }); }, destroy : function( ) { return this.each(function(){ $(window).unbind('.tooltip'); }) }, reposition : function( ) { // ... }, show : function( ) { // ... }, hide : function( ) { // ... }, update : function( content ) { // ... } }; $.fn.tooltip = function( method ) { if ( methods[method] ) { return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on jQuery.tooltip' ); } }; })( jQuery );
$('#fun').tooltip(); // Some time later... $('#fun').tooltip('destroy');
In this example, when the tooltip is initialized with the init method, it binds the reposition method to the resize event of the window under the namespace 'tooltip'. Later, if the developer needs to destroy the tooltip, we can unbind the events bound by the plugin by passing its namespace, in this case 'tooltip', to the unbind method. This allows us to safely unbind plugin events without accidentally unbinding events that may have been bound outside of the plugin.
Data
Often times in plugin development, you may need to maintain state or check if your plugin has already been initialized on a given element. Using jQuery's data method is a great way to keep track of variables on a per element basis. However, rather than keeping track of a bunch of separate data calls with different names, it's best to use a single object literal to house all of your variables, and access that object by a single data namespace.
(function( $ ){ var methods = { init : function( options ) { return this.each(function(){ var $this = $(this), data = $this.data('tooltip'), tooltip = $('<div />', { text : $this.attr('title') }); // If the plugin hasn't been initialized yet if ( ! data ) { /* Do more setup stuff here */ $(this).data('tooltip', { target : $this, tooltip : tooltip }); } }); }, destroy : function( ) { return this.each(function(){ var $this = $(this), data = $this.data('tooltip'); // Namespacing FTW $(window).unbind('.tooltip'); data.tooltip.remove(); $this.removeData('tooltip'); }) }, reposition : function( ) { // ... }, show : function( ) { // ... }, hide : function( ) { // ... }, update : function( content ) { // ...} }; $.fn.tooltip = function( method ) { if ( methods[method] ) { return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on jQuery.tooltip' ); } }; })( jQuery );
Using data helps you keep track of variables and state across method calls from your plugin. Namespacing your data into one object literal makes it easy to access all of your plugin's properties from one central location, as well as reducing the data namespace which allows for easy removal if need be.
Summary and Best Practices
Writing jQuery plugins allows you to make the most out of the library and abstract your most clever and useful functions out into reusable code that can save you time and make your development even more efficient. Here's a brief summary of the post and what to keep in mind when developing your next jQuery plugin:
Always wrap your plugin in a closure: (function( $ ){ /* plugin goes here */ })( jQuery );
Don't redundantly wrap the this keyword in the immediate scope of your plugin's function
Unless you're returning an intrinsic value from your plugin, always have your plugin's function return the this keyword to maintain chainability.
Rather than requiring a lengthy amount of arguments, pass your plugin settings in an object literal that can be extended over the plugin's defaults.
Don't clutter the jQuery.fn object with more than one namespace per plugin. Always namespace your methods, events and data.
Translations
If you have translated this article or have some similar one on your blog post a link here. Please mark Full Translated articles with (t) and similar ones with (s).