【譯】jQuery插件編寫

最新內容請查看:http://leeyee.github.io/blog/2012/12/02/jquery-plugins-authoring 

 

jQuery插件編寫(Plugins/Authoring)

原文地址http://docs.jquery.com/Plugins/Authoring

So you've become comfortable with jQuery and would like to learn how to write your own plugins. Great! You're in the right spot. Extending jQuery with plugins and methods is very powerful and can save you and your peers a lot of development time by abstracting your most clever functions into plugins. This post will outline the basics, best practices, and common pitfalls to watch out for as you begin writing your plugin.

當你已經熟練掌握jQuery並且想學習如何編寫屬於自己的插件程序時,你可以參看這篇文章。使用插件程序和方法擴展jQuery是非常強大的。你可以將一些想法抽象爲函數並封裝到插件中以便爲你和你的同事節省許多開發時間。

目錄(CONTENTS)

  1. 入門知識(Getting Started)

  2. 上下文(Context)

  3. 基本要素(The Basics)

  4. 保持可鏈通性(Maintaining Chainability)

  5. 默認值和可選項(Defaults and Options)

  6. 命名空間(Namespacing)

    6.1 插件方法(Plugin Methods)

    6.2 事件(Events)

    6.3 數據(Data)

  7. 總結和最佳實踐(Summary and Best Practices)

  8. 翻譯(Translations)

入門知識(Getting Started)

To write a jQuery plugin, start by adding a new function property to the jQuery.fn object where the name of the property is the name of your plugin:

編寫jQuery插件,是從添加一個新的函數屬性到jQuery.fn對象開始的,其中新的函數屬性名爲你要編寫插件的名稱:

jQuery.fn.myPlugin = function() {
    // Do your awesome plugin stuff here
};

But wait! Where's my awesome dollar sign that I know and love? It's still there, however to make sure that your plugin doesn't collide with other libraries that might use the dollar sign, it's a best practice to pass jQuery to an IIFE (Immediately Invoked Function Expression) that maps it to the dollar sign so it can't be overwritten by another library in the scope of its execution.

但是請等一下,我們喜愛的萬能$符號在哪裏?它仍然在哪裏,然而爲了確保你的插件不會與其他可能使用$符號的javascript庫衝突,一個最佳的實踐是將jQuery對象當做參數傳遞到一個可立即執行的函數表達式中(IIFE),該函數表達式會通過$符號來映射jQuery對象,這樣在該函數表達式內,$符號將不會被在其可執行的作用域中的其他庫所覆蓋。

(function( $ ) {
    $.fn.myPlugin = function() {
        // Do your awesome plugin stuff here
    };
})( jQuery );

Ah, that's better. Now within that closure, we can use the dollar sign in place of jQuery as much as we like.

這樣就可以了。現在通過閉包,我們就能使用我們喜愛的$符號代替jQuery對象了。

上下文(Context)

Now that we have our shell we can start writing our actual plugin code. But before we do that, I'd like to say a word about context. In the immediate scope of the plugin function, the this keyword refers to the jQuery object the plugin was invoked on. This is a common slip up due to the fact that in other instances where jQuery accepts a callback, the this keyword refers to the native DOM element. This often leads to developers unnecessarily wrapping the this keyword (again) in the jQuery function.

現在,有了插件外殼,我們就可以開始編寫真正的插件代碼了。但是在此之前,我想要談談上下文。在插件函數的當前域中,this 關鍵字是指代被調用插件中的jQuery對象的。這是一個常見的疏忽,因爲在jQuery接受回調的其他情況中,this 關鍵字指代的是原生DOM元素。這常常會導致開發人員不必要地使用jQuery函數來包裝 this 關鍵字。

(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),
        // 因爲this已經是一個jQuery object對象了
        // $(this) 等價於 $($('#element'));
        this.fadeIn('normal', function(){
            // the this keyword is a DOM element
        });
    };
})( jQuery );
$('#element').myPlugin();
基本要素(The Basics)

Now that we understand the context of jQuery plugins, let's write a plugin that actually does something.

現在,在瞭解了jQuery插件的上下文後,我們來編寫一個實現了一些功能的插件。

(function( $ ){
    $.fn.maxHeight = function() {
        var max = 0;
        this.each(function() {
            max = Math.max( max, $(this).height() );
        });
        return max;
    };
})( jQuery );
// Returns the height of the tallest div
var tallest = $('div').maxHeight(); 

This is a simple plugin that leverages .height() to return the height of the tallest div in the page.

這是一個簡單的插件,通過調用 .height() 來獲取頁面最高div元素的高度。

保持可鏈通性(Maintaining Chainability)

The previous example returns an integer value of the tallest div on the page, but often times the intent of a plugin is simply modify the collection of elements in some way, and pass them along to the next method in the chain. This is the beauty of jQuery's design and is one of the reasons jQuery is so popular. So to maintain chainability in a plugin, you must make sure your plugin returns the this keyword.

前面的示例返回一個整數值。但是大多數時候編寫插件的意圖是在某種程度上簡單的修改一系列的元素,並且傳遞他們到另一個方法。這是jQuery優雅的設計之處和如此受歡迎的原因之一。因此爲了維護插件中代碼的可鏈通性,必須保證插件返回 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');

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.

因爲插件在其當前的作用範圍內返回了 this 關鍵字,所以就維持了可鏈通性,返回的jQuery對象集合也就可以繼續被jQuery方法操作,比如 .css。假如插件不能返回一個內在值,那麼就應該總是返回在其當前作用範圍內的this 關鍵字。另外,如你所想,插件調用中的參數也傳遞到了當前作用範圍內的插件函數中。所以在上述示例中,字符串'width'就變成了插件函數中的一個 type 參數。

默認值和可選項(Defaults and Options)

For more complex and customizable plugins that provide many options, it's a best practice to have default settings that can get extended (using $.extend) when the plugin is invoked. So instead of calling a plugin with a large number of arguments, you can call it with one argument which is an object literal of the settings you would like to override. Here's how you do it.

對於許多包含可選項的複雜可定製化插件,最好的方式是有一些能在被調用時可擴展(使用$.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'
});

In this example, after calling the tooltip plugin with the given options, the default location setting gets overridden to become 'left', while the background-color setting remains the default 'blue'. So the final settings object ends up looking like this:

在這例子中,當使用給定的可選項調用 tooltip 插件方法後,默認的 'location' 屬性會被設置成 'left', 而 'background-color' 屬性則保持不變。所以最終的 'settings' 對象看來是這樣的:

{
    'location'         : 'left',
    'background-color' : 'blue'
}

This is a great way to offer a highly configurable plugin without requiring the developer to define all available options.

這是一種很好的方式。它提供了一個高度可配置的插件,而不需要開發人員去定義所有的可用選項。

命名空間(Namespacing)

Properly namespacing your plugin is a very important part of plugin development. Namespacing correctly assures that your plugin will have a very low chance of being overwritten by other plugins or code living on the same page. Namespacing also makes your life easier as a plugin developer because it helps you keep better track of your methods, events and data.

爲你的插件設置一個適當的命名空間是插件開發中非常重要的一部分。合理的命名空間可以降低你的插件被另一些插件或者當前頁面上的代碼覆蓋的機率。命名空間也可以讓你的開發變得容易,它可以讓你更好的跟蹤方法、事件和數據。

插件方法(Plugin Methods)

Under no circumstance should a single plugin ever claim more than one namespace in the jQuery.fn object.

在任何情況下,一個單獨的插件在jQuery.fn對象上的命名空間都不應超過一個。

(function( $ ){
    $.fn.tooltip = function( options ) { 
        // THIS
    };
    $.fn.tooltipShow = function( ) {
        // IS
    };
    $.fn.tooltipHide = function( ) { 
        // BAD
    };
    $.fn.tooltipUpdate = function( content ) { 
        // !!!  
    };
})( jQuery );

This is a discouraged because it clutters up the $.fn namespace. To remedy this, you should collect all of your plugin's methods in an object literal and call them by passing the string name of the method to the plugin.

這樣做是不被提倡的,因爲它會使$.fn命名空間變得混亂。爲了解決這個問題,你應該將所有插件的方法存放在一個對象字面量中,然後通過傳遞方法的字符串名稱來調用。

(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.

這種類型的插件結構允許在插件的父閉包中封裝所有的方法,然後通過先傳遞方法的字符串名稱,再傳遞其他一些這個方法可能用到的參數來調用。這種方式的方法封裝和架構在jQuery插件社區是一種標準並被無數的插件採用,包括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 “.< namespace >”the type of event you're binding.

對於bind方法一個鮮有人知的特性是允許爲綁定的事件聲明命名空間。假如你的插件綁定了一個事件,一個好的處理方式是爲該事件添加命名空間。通過這種方式,假如隨後你需要解除綁定(unbind),就可以在不影響其他可能已經綁定了相同類型事件的情況下解除綁定。你可以通過添加.<namespace>給你要綁定的事件添加命名空間。

(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.

在這個示例中,當 tooltip 使用 init 方法初始化時,他會綁定 reposition 方法到window對象的 resize 事件上,這些都是在 'tooltip' 命名空間下進行的。之後,如果開發人員需要銷燬 tooltip ,就可以通過傳遞命名空間來解除該命名空間下綁定的事件。這讓我們可以安全的解除通過插件綁定的事件,而不用擔心將通過插件外其他方式綁定的事件也解除掉。

數據(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.

通常在插件開發中,可能需要維護狀態或者檢測插件在給定的元素上是否已被初始化。使用jquery 的 data 方法可以很好的跟蹤基於每一個元素的變量。然而,相比跟蹤大量有着不同名字的單獨數據,還不如使用一個單獨對象的字面量去存儲所有變量並通過單一數據命名空間來訪問此對象。

(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:

編寫jQuery插件允許你實現最有利用效率的庫,允許你抽象有用的函數到可重用代碼中,從而爲你節省大量開發時間,使你的開發團隊更加高效。下面是一些一些在開發jQuery插件過程中應時刻牢記的簡要的總結:

  1. Always wrap your plugin in a closure: (function( $ ){ /* plugin goes here */ })( jQuery );

    始終將你的插件封裝在一個閉包中:(function($) { /* plugin goes here */ })( jQuery );

  2. Don't redundantly wrap the this keyword in the immediate scope of your plugin's function

    不要在插件函數的當前作用域中使用$(this)多餘的處理 this 關鍵字。

  3. Unless you're returning an intrinsic value from your plugin, always have your plugin's function return the this keyword to maintain chainability.

    除非返回一個內部值,否則總是讓你的插件函數返回 this 關鍵字以保持鏈通性。

  4. 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.

    不要爲你的插件函數定義過多的參數,而是通過傳遞一個可被擴展到插件默認值的對象字面量來傳遞參數。

  5. Don't clutter the jQuery.fn object with more than one namespace per plugin.

    保證每個插件中只有一個 jQuery.fn 對象的命名空間

  6. 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).

TOP

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章