jQuery - Plugins

司空均
2023-12-01

jQuery 插件是用于扩展 jQuery 原型对象的新方法。通过扩展原型对象,可以使所有 jQuery 对象继承新加入的方法。如上所述,无论何时调用 jQuery() 都会创建一个新的 jQuery 对象,并且继承所有 jQuery 的方法。


Finding & Evaluating Plugins

jQuery 最有名的一方面是它广泛的插件生态系统。jQuery 插件质量参差不齐,有些良好,有些差劲。jQuery UI 由 jQuery 小组维护,此插件的质量相当于 jQuery 本身。

寻找插件可以通过搜索或者在 jQuery Plugins Registry 寻找。


How to Create a Basic Plugin

How jQuery Works 101:jQuery Object Methods

jQuery 对象方法

$( "a" ).css( "color", "red" );

无论何时使用 $ 函数选择元素,它都会返回一个 jQuery 对象。该对象包含所有的方法和所有适配选择器的元素。jQuery 对象从 $.fn 对象获取这些方法。

Basic Plugin Authoring

假设创建一个插件,使一组检索到的元素中的文本变为绿色。实现方式为将名为 greenify 的函数加入到 $.fn 中。

$.fn.greenify = function() {
    this.css( "color", "green" );
};
 
$( "a" ).greenify(); // Makes all the links green.

注意:此时调用 .css() 或其他方法,是通过 this 而不是 $(this),因为此时 greenify 函数是与 .css() 相同的对象的一部分。

 

Chaining

函数的调用若能返回 jQuery 对象,就可以使用方法链。例如:无参数的 .width() 返回值为选中元素的宽度,故不能加入方法链中使用。若要使插件方法,使用方法链,那么就要返回对象。

$.fn.greenify = function() {
    this.css( "color", "green" );
    return this;
}
 
$( "a" ).greenify().addClass( "greenified" );

Protecting the $ Alias and Adding Scope

$ 变量在 JavaScript 库中非常普及,若在使用 jQuery 的同时也在使用其他 JavaScript 库,那就不得不通过 jQuery.noConflict() 来使 jQuery 不使用 $。此时也会破坏插件中使用 $ 别名对 jQuery 的指代。若要使其他 JavaScript 库,并且 jQuery 继续使用别名 $,就要将所有代码放入:Immediately Invoked Function Expression,随后传入 jQuery 函数,并且将参数命名为:$。

(function ( $ ) {
 
    $.fn.greenify = function() {
        this.css( "color", "green" );
        return this;
    };
 
}( jQuery ));

此外,Immediately Invoked Function 的主要意图是,使我们拥有自己的私有变量。

(function ( $ ) {
 
    var shade = "#556b2f";
 
    $.fn.greenify = function() {
        this.css( "color", shade );
        return this;
    };
 
}( jQuery ));

Minimizing Plugin Footprint

减少插件的占用空间,此种方式可以减少自有插件被覆盖的可能性,同时减少自有插件覆盖其他插件的可能性。

不良写法:

(function( $ ) {
 
    $.fn.openPopup = function() {
        // Open popup code.
    };
 
    $.fn.closePopup = function() {
        // Close popup code.
    };
 
}( jQuery ));

优良写法:

(function( $ ) {
 
    $.fn.popup = function( action ) {
 
        if ( action === "open") {
            // Open popup code.
        }
 
        if ( action === "close" ) {
            // Close popup code.
        }
 
    };
 
}( jQuery ));

Useing the each() Method

典型的 jQuery 对象中会包含许多 DOM 元素的参考,这就是 jQuery 对象经常被视为集合的原因。

$.fn.myNewPlugin = function() {
 
    return this.each(function() {
        // Do something to each element here.
    });
 
};

注意:此时的返回值为:.each() 而不是 this。

Accepting Options

当插件变得更加复杂时,可以通过传入选项,来定制化插件。

(function ( $ ) {
 
    $.fn.greenify = function( options ) {
 
        // This is the easiest way to have default options.
        var settings = $.extend({
            // These are the defaults.
            color: "#556b2f",
            backgroundColor: "white"
        }, options );
 
        // Greenify the collection based on the settings variable.
        return this.css({
            color: settings.color,
            backgroundColor: settings.backgroundColor
        });
 
    };
 
}( jQuery ));

// 使用示例
$( "div" ).greenify({
    color: "orange"
});

Putting It Together

(function( $ ) {
 
    $.fn.showLinkLocation = function() {
 
        this.filter( "a" ).each(function() {
            var link = $( this );
            link.append( " (" + link.attr( "href" ) + ")" );
        });
 
        return this;
 
    };
 
}( jQuery ));

// 优化
(function( $ ) {
 
    $.fn.showLinkLocation = function() {
 
        this.filter( "a" ).append(function() {
            return " (" + this.href + ")";
        });
 
        return this;
 
    };
 
}( jQuery ));
 
// Usage example:
$( "a" ).showLinkLocation();
<!-- Before plugin is called: -->
<a href="page.html">Foo</a>
 
<!-- After plugin is called: -->
<a href="page.html">Foo (page.html)</a>

Advanced Plugin Concepts

Provide Public Access to Default Plugin Settings

暴露插件的默认设置,使用户花最少的代码,来覆盖/定制化插件。

// 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"
});

Provide Public Access to Secondary Function  as Applicable

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

Keep Private Functions Private

暴露插件的部分,已提供强大的功能。但是要小心的考虑,要暴露插件中那一部分的执行。一旦暴露这些,那么对这些呼叫参数和语法的改变可能会破坏向后的兼容性。作为基本规则,弱不确定应当暴露某部分功能,那么先不要这样做。

那么如何定义更多的函数,而不混淆命名空间,并且不用暴露实现?解决这些问题,通过在插件中加入称为“debug”的函数。该 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 );

"debug"方法不能在闭包的外部访问,因此该方法对于执行中是私有的。

Bob and Sue

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

Don't Create Plugin-specific Syntax 不要创建插件特定语法

Give Full Control of Elements 给予对元素的全权控制。若插件创建了在 DOM 中使用的元素,最好提供插件用户用以访问这些元素的途径。例如:提供 IDs 或 classes

不良实现:

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

允许用户访问,甚至操纵这些讯息,可以存贮这些信息于变量中,并将该变量放入插件的设置中。

良好实现:

// 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 );

此时 .extend() 方法将会递归循环这个嵌套的对象,得到合并 defaults 和 options 的对象,合并时 options 拥有较高的优先权。

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

Provide Callback Capabilities

若插件是由事件驱动,那么最好为每个事件都提供回调函数。

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

$( "ul.imgs li" ).superGallery({
    onImageShow: function() {
        $( this ).after( "<span>" + $( this ).attr( "longdesc" ) + "</span>" );
    },
 
    // ... other options ...
});

Remember, It's a Compromise 插件不可能在所有情形下都工作良好,需要考虑的是:

  • Flexibility 可以处理多少种情形
  • Size 使插件的大小符合它的功能层级
  • Performance 提供优良的性能

Writing Stateful Plugins with the jQuery UI Widget Factory

当前许多 jQuery 插件是无状态的,在元素上呼叫这些插件,只是对互动作用的扩展,有一大堆功能不适合插件的基本模式。

为了填补这个空白,jQuery UI 实现了一个更加高级的插件系统。该新系统管理状态,允许更多功能通过单个插件暴露,并提供各种各样的可扩展点。该系统被称作 Widget Factory (小部件工厂),并且是作为 jQuery UI 1.8 的一部分来暴露,但是它可以独立于 jQuery UI 来使用。

示例通过创建进度条插件,来展示小部件工厂的功能。

起初,创建一个只允许设置一次进度的进度条。实现此功能,通过呼叫两个参数的 jQuery Widget:创建的插件的名称、包含支持此插件功能的对象。

当此插件被呼叫时,将创建该插件的新的实例,并且所有的功能都将在该实例的上下文中执行。此种类型的插件,同标准的 jQuery 插件有两点重要的不同:首先,该上下文是一个对象,而不是 DOM 元素;其次,该上下文总是一个单独的对象,永远不会是集合。

$.widget( "nmk.progressbar", {
 
    _create: function() {
        var progress = this.options.value + "%";
        this.element.addClass( "progressbar" ).text( progress );
    }
 
});

此种插件的名称必须包含命名空间;并且命名空间的深度只被限定在一层。小部件工程提供两个属性:this.element 是明确包含一个元素的 jQuery 对象。若该插件被包含多个元素的 jQuery 对象呼叫,那么将会为每个元素创建彼此分离的此插件实例,并且每个实例都将拥有它自己的 this.element。this.options 是一个哈希表,包含对此插件选项设定的键/值对。

注意:ui 命名空间,是官方 jQuery UI 插件的保留字。

将选项传入小部件中:

$( "<div />" ).appendTo( "body" ).progressbar({ value: 20 });

当呼叫 jQuery.widget 时,其会通过向 jQuery.fn 添加方法来扩展 jQuery。呼叫此小部件函数的名称,基于定义时的命名空间之后后缀的名称。示例为:jQuery.fn.progressbar 。可以对小部件的任意选项,指定默认值。为小部件指定默认值:

$.widget( "nmk.progressbar", {
 
    // Default options.
    options: {
        value: 0
    },
 
    _create: function() {
        var progress = this.options.value + "%";
        this.element.addClass( "progressbar" ).text( progress );
    }
 
});

Adding Methods to a Widget

现在可以初始化进度条了,我们通过呼叫插件实例的方法以添加执行动作的能力。

$.widget( "nmk.progressbar", {
    options: {
        value: 0
    },
 
    _create: function() {
        var progress = this.options.value + "%";
        this.element.addClass("progressbar").text( progress );
    },
 
    // Create a public method.
    value: function( value ) {
 
        // No value passed, act as a getter.
        if ( value === undefined ) {
 
            return this.options.value;
 
        // Value passed, act as a setter.
        } else {
 
            this.options.value = this._constrain( value );
            var progress = this.options.value + "%";
            this.element.text( progress );
 
        }
 
    },
 
    // 带下划线的为私有方法
    _constrain: function( value ) {
 
        if ( value > 100 ) {
            value = 100;
        }
 
        if ( value < 0 ) {
            value = 0;
        }
 
        return value;
    }
 
});

方法呼叫方式:

var bar = $( "<div />" ).appendTo( "body").progressbar({ value: 20 });
 
// Get the current value.
alert( bar.progressbar( "value" ) );
 
// Update the value.
bar.progressbar( "value", 50 );
 
// Get the current value again.
alert( bar.progressbar( "value" ) );

Working with Widget Options

对于插件自动的公共可用的方法之一是:option 方法。该方法允许在初始化之后来取得和设置选项。向该方法传入名称来取得值;向该方法传入名称和值来设定内容。可以指定特定的 _setOption 方法来响应插件选项的改变。

$.widget( "nmk.progressbar", {
 
    options: {
        value: 0
    },
 
    _create: function() {
        this.element.addClass( "progressbar" );
        this._update();
    },
 
    _setOption: function( key, value ) {
        this.options[ key ] = value;
        this._update();
    },
 
    _update: function() {
        var progress = this.options.value + "%";
        this.element.text( progress );
    }
 
});

Adding Callbacks

通过添加回调函数,是使插件变得可扩展的非常简单的方式。_trigger 方法触发三个参数:回调的名称、启动回调的本地事件对象、同该事件相关的 hash 数据。

$.widget( "nmk.progressbar", {
 
    options: {
        value: 0
    },
 
    _create: function() {
        this.element.addClass( "progressbar" );
        this._update();
    },
 
    _setOption: function( key, value ) {
        this.options[ key ] = value;
        this._update();
    },
 
    _update: function() {
        var progress = this.options.value + "%";
        this.element.text( progress );
        if ( this.options.value == 100 ) {
            this._trigger( "complete", null, { value: 100 } );
        }
    }
 
});

回调函数本质上是附件的选项,所以也可以取得和设置回调函数。插件的停止功能,通过 event.preventDefault() 或 return false,来取消本地事件。若用户取消回调函数,_trigger 方法将返回 false。

绑定小部件事件:

var bar = $( "<div />" ).appendTo( "body" ).progressbar({
 
    complete: function( event, data ) {
        alert( "Callbacks are great!" );
    }
 
}).bind( "progressbarcomplete", function( event, data ) {
 
     alert( "Events bubble and support many handlers for extreme flexibility." );
 
     alert( "The progress bar value is " + data.value );
 
});
 
bar.progressbar( "option", "value", 100 );

The Widget Factory: Under the Hood

当呼叫 jQuery.widget 时,它会为插件创建构造函数,并且以传入的原型蓝本来设置插件实例对象。当插件实例对象被创建之后,它通过 jQuery.data 存贮在原始 DOM 元素上,键名为插件的全程。例如:jQuery UI dialog widget 为 "ui-dialog"。

由于插件实例同 DOM 元素直接连接,所以可以使用元素对象直接访问插件实例。

var bar = $( "<div />")
    .appendTo( "body" )
    .progressbar()
    .data( "nmk-progressbar" );
 
// Call a method directly on the plugin instance.
bar.option( "value", 50 );
 
// Access properties on the plugin instance.
alert( bar.options.value );

持有插件的构造函数和原型蓝本的最大益处是可以简易的扩展插件。在插件的原型蓝本上添加或变更方法,可以改变所有插件实例对象的行为。例如:

$.nmk.progressbar.prototype.reset = function() {
    this._setOption( "value", 0 );
};

Cleaning Up

通过 _destroy 方法,来应用和停用插件。从元素上移除插件时,_destroy 方法会被自动调用。

$.widget( "nmk.progressbar", {
 
    options: {
        value: 0
    },
 
    _create: function() {
        this.element.addClass("progressbar");
        this._update();
    },
 
    _setOption: function( key, value ) {
        this.options[ key ] = value;
        this._update();
    },
 
    _update: function() {
        var progress = this.options.value + "%";
        this.element.text( progress );
        if ( this.options.value === 100 ) {
            this._trigger( "complete", null, { value: 100 } );
        }
    },
 
    _destroy: function() {
        this.element
            .removeClass( "progressbar" )
            .text( "" );
    }
 
});

Widget Factory 是创建有状态插件的唯一方式。

 类似资料: