「jQuery外掛開發日記」(二)高階外掛理念 – [翻譯]

JasonKidd發表於2015-08-30

_Advanced Plugin Concepts_,翻譯自 jQuery 官方網站。

預設設定的共有介面

對於上一篇文章最後的程式碼,我們可以改進,也應該改進的地方就是,為我們的外掛的預設設定提供共有介面,使得使用者可以直接更改預設設定。

這樣做的好處就是可以讓使用者用最少的程式碼量來定製我們的外掛。這次的例子是:

// Plugin definition.
$.fn.hilight = function( options ) {
 
    // Extend our default options with those provided.
    // Note that the first argument 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 needs 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 elem = $( this );
 
        // ...
 
        var markup = elem.html();
 
        // Call our format function.
        markup = $.fn.hilight.format( markup );
 
        elem.html( markup );
 
    });
 
};
 
// Define our format function.
$.fn.hilight.format = function( txt ) {
    return "<strong>" + txt + "</strong>";
};

我們也可以在設定中間允許一個回撥函式,來覆蓋預設的 format 函式, 這也是一個支援定製的非常棒的方法。
使用這些技術,其他人可以非常方便的定製你的外掛,然後釋出。換句話說,其他人可以為你的外掛來寫外掛。

考慮到這篇文章裡這個蒼白的例子並不是非常具有說服力,一個現實的例子就是 Cycle Plugin。這個外掛是一個幻燈片外掛,內建非常多切換特效,像滾動、滑動、淡出等。但是實際上它不可能定義每個人想要的效果,那麼外掛的擴充套件性就非常重要了。

Cycle Plugin 對外暴露了一個 transitions 物件。 在這裡使用者可以自己定義他們的切換特效。

$.fn.cycle.transitions = {
 
    // ...
 
};

保持私有函式私有

對外提供一部分你的外掛的公共介面確實很強大,但是你要想清楚哪些部分需要提供給公共介面,哪些不需要。當函式一旦暴露在外,任何對引數和語義(函式的功能)的更改都會摧毀向後相容性。一般來說,如果你不確定一個方法是否應該設為公有,那麼也許答案是不。

所以我們如何在不搞亂名稱空間並且保持私有的情況下定義更多的函式呢?這就是閉包的事情了。

為了展示如何解決這個問題,我們在我們的外掛里加了一個函式 debug。這個函式在 console 中輸出選定的物件。我們將整個外掛用一個函式包裹起來(上一篇文章也有提到)。

// 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.length );
        }
    };
 
    // ...
 
// End of closure.
 
})( jQuery );

Bob & Sue(例項)

Bob 已經建立了一個新的畫廊外掛(叫 “superGallery”)。這個外掛使得一系列圖片變得可導航的。Bob 同樣還新增了一些動畫效果使得外掛變得更加有趣。他想讓他的外掛獲得最大程度的可定製性,所以他寫出了下面的程式碼:

jQuery.fn.superGallery = function( options ) {
 
    // Bob`s default settings:
    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() {
        // Plugin code would go here...
    });
 
};

你想到的第一件事情可能就是,這個外掛該有多大才能實現這麼多可定製功能。這個外掛的體積可能完全沒必要這麼大。

Bob 對他的外掛非常滿意。他覺得他的外掛會在不同的情景之下會是一個通用的解決方案。

Sue 決定去用一用這個新外掛。她設定了所有的選項,但是她意識到,如果圖片的寬度以低速變化的話,會獲得更加好的效果。她趕緊去搜尋了 Bob 的文件,但是並沒有找到 animateWidthDuration 類似的選項。

你看到問題了嗎?

問題的關鍵是並不是你的外掛有多少個選項,而是有什麼選項。

Bob 做的有點兒過了。他提供的定製性看起來很高,其實非常低。特別是考慮到一個人可能想要在這個外掛中控制的所有特性。Bob 犯了個錯誤,它提供了非常多荒誕的選項,殊不知讓他的外掛變得更難定製了。

一個更好的模型

所以現在非常明顯了,Bob 需要一個新的定製模型。一個並沒有放棄必須的控制和抽象細節的定製模型。

這裡有一些建議,可以讓你更好的建立一個可定製化外掛。

不要建立自己的語法

使用你的外掛的開發者不應該要去學一門新的語言或新的技術,只要搞定他們的工作。

Bob 他為 delay 這個選項提供了最大的定製性。他設定了4個不同的延遲:

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;
 
}

這導致不僅僅使用者所能控制的延遲水平變少了,還花費了比較多的空間。20行程式碼僅僅就為了定義一個延遲時間,有點兒多了。

一個更加好的方式就是讓使用者自己傳入延遲的時間。

元素的完全控制權

如果你的外掛在DOM中建立了一些元素。那麼你最好讓使用者有方法去控制它。有些時候這意味著提供給使用者傳入ID或者類名的方法。但是注意你的外掛不應該全域性依賴這些。

一個不好的實現:

// Plugin code
$( "<div class=`gallery-wrapper` />" ).appendTo( "body" );

一個好一點兒的實現:

// Retain an internal reference:
var wrapper = $( "<div />" )
    .attr( settings.wrapperAttrs )
    .appendTo( settings.container );
 
// Easy to reference later...
wrapper.append( "..." );

注意我們使用了 .attr() 來增加特定的屬性。所以我們的設定應該像這樣:

var defaults = {
    wrapperAttrs : {
        class: "gallery-wrapper"
    },
    // ... rest of settings ...
};
 
// We can use the extend method to merge options/settings as usual:
// But with the added first parameter of TRUE to signify a DEEP COPY:
var settings = $.extend( true, {}, defaults, options );

對於CSS 也可以使用同樣的方法來實現:

var defaults = {
    wrapperCSS: {},
    // ... rest of settings ...
};
 
// Later on in the plugin where we define the wrapper:
var wrapper = $( "<div />" )
    .attr( settings.wrapperAttrs )
    .css( settings.wrapperCSS ) // ** Set CSS!
    .appendTo( settings.container );

提供回撥函式

如果你的外掛是事件驅動的話,最好為每個事件提供回撥函式。

var defaults = {
 
    // We define an empty anonymous function so that
    // we don`t need to check its existence before calling it.
    onImageShow : function() {},
 
    // ... rest of settings ...
 
};
 
// Later on in the plugin:
 
nextButton.on( "click", showNextImage );
 
function showNextImage() {
 
    // Returns reference to the next image node
    var image = getNextImage();
 
    // Stuff to show the image here...
 
    // Here`s the callback:
    settings.onImageShow.call( image );
}

記住,這是一個權衡問題

你的外掛不可能在所有的情況下都能工作。同樣的,當你提供很少的控制的方法的時候,它也有可能沒啥用。所以,權衡是非常重要的事情。以下有三點:

  • 靈活性:你的外掛要處理多少種情況?

  • 大小:你的外掛大小和它的功能相匹配嗎?

  • 效能:你的外掛無論什麼情況都會處理設定選項嗎?影響速度嗎?值得嗎?

相關文章