jQuery 插件是用于扩展 jQuery 原型对象的新方法。通过扩展原型对象,可以使所有 jQuery 对象继承新加入的方法。如上所述,无论何时调用 jQuery() 都会创建一个新的 jQuery 对象,并且继承所有 jQuery 的方法。
jQuery 最有名的一方面是它广泛的插件生态系统。jQuery 插件质量参差不齐,有些良好,有些差劲。jQuery UI 由 jQuery 小组维护,此插件的质量相当于 jQuery 本身。
寻找插件可以通过搜索或者在 jQuery Plugins Registry 寻找。
jQuery 对象方法
$( "a" ).css( "color", "red" );
无论何时使用 $ 函数选择元素,它都会返回一个 jQuery 对象。该对象包含所有的方法和所有适配选择器的元素。jQuery 对象从 $.fn 对象获取这些方法。
假设创建一个插件,使一组检索到的元素中的文本变为绿色。实现方式为将名为 greenify 的函数加入到 $.fn 中。
$.fn.greenify = function() {
this.css( "color", "green" );
};
$( "a" ).greenify(); // Makes all the links green.
注意:此时调用 .css() 或其他方法,是通过 this 而不是 $(this),因为此时 greenify 函数是与 .css() 相同的对象的一部分。
函数的调用若能返回 jQuery 对象,就可以使用方法链。例如:无参数的 .width() 返回值为选中元素的宽度,故不能加入方法链中使用。若要使插件方法,使用方法链,那么就要返回对象。
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
}
$( "a" ).greenify().addClass( "greenified" );
$ 变量在 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 ));
减少插件的占用空间,此种方式可以减少自有插件被覆盖的可能性,同时减少自有插件覆盖其他插件的可能性。
不良写法:
(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 ));
典型的 jQuery 对象中会包含许多 DOM 元素的参考,这就是 jQuery 对象经常被视为集合的原因。
$.fn.myNewPlugin = function() {
return this.each(function() {
// Do something to each element here.
});
};
注意:此时的返回值为:.each() 而不是 this。
当插件变得更加复杂时,可以通过传入选项,来定制化插件。
(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"
});
(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>
暴露插件的默认设置,使用户花最少的代码,来覆盖/定制化插件。
// 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"
});
// 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>";
};
暴露插件的部分,已提供强大的功能。但是要小心的考虑,要暴露插件中那一部分的执行。一旦暴露这些,那么对这些呼叫参数和语法的改变可能会破坏向后的兼容性。作为基本规则,弱不确定应当暴露某部分功能,那么先不要这样做。
那么如何定义更多的函数,而不混淆命名空间,并且不用暴露实现?解决这些问题,通过在插件中加入称为“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"方法不能在闭包的外部访问,因此该方法对于执行中是私有的。
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 );
若插件是由事件驱动,那么最好为每个事件都提供回调函数。
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 插件不可能在所有情形下都工作良好,需要考虑的是:
当前许多 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 );
}
});
现在可以初始化进度条了,我们通过呼叫插件实例的方法以添加执行动作的能力。
$.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" ) );
对于插件自动的公共可用的方法之一是: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 );
}
});
通过添加回调函数,是使插件变得可扩展的非常简单的方式。_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 );
当呼叫 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 );
};
通过 _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 是创建有状态插件的唯一方式。