写一个JQuery扩展很容易:
(function($){
$.fn.TestComponent = function(){
return this.each(function() {
console.log('init');
});
};
})(jQuery);
这样就行了,调用方法如下:
但是通常一个功能完善的扩展不仅仅是调用一次初始化就完事的,假设是要扩展出一个基本组件,你用上述方式扩展生成了,还需要提供接口供用户访问数据或者侦听事件响应或者回调等。$('#test').TestComponent();
通常一个组件提供的接口可能如下几种:
1、访问属性(譬如获取组件当前值)
2、分发事件(譬如用户更改值)
3、一些特殊的回调(譬如加载完成等,跟事件类似)
4、设置某些属性或者调用某些动作方法。
问题来了,上面这种写法,调用一次之后返回的就是一个jQuery对象。
对于回调好处理,作为一个初始化参数传进去就可以了,但是要想随时随地访问内部属性以及侦听事件、调用接口,这种写法已经没有机会了。
以下是我总结的几种方法,1 2是我自己原先的考虑,3是jqueryUI的写法:
方法1、把组件变成一个独立类,不再是jQuery的扩展,譬如
调用的时候就这样(function(window){
var TestComponent = function(jqueryItem) {
this.item = jqueryItem;
this.item.each(function(){
console.log('init');
});
this.name = 'testName';
};
TestComponent.prototype.sayHello = function() {
console.log('hello');
};
window.TestComponent = TestComponent;
})(window);
这样的坏处一方面不是通常的jQuery扩展的写法,另一方面还需要用一个变量来存储这个组件实例。var test = new TestComponent($('#test'));
test.sayHello();
console.log(test.name);
方法2、用jQuery扩展的方式,但是不是返回jQuery对象
这样的坏处一方面不能链式调用jQuery对象的其他方法,另一方面也需要用一个变量来存储这个组件实例。(function($){
var TestComponent = function(jqueryItem) {
this.item = jqueryItem;
this.item.each(function(){
console.log('init');
});
this.name = 'testName';
};
TestComponent.prototype.sayHello = function() {
console.log('hello');
};
$.fn.TestComponent = function(){
var test;
this.each(function() {
test = new TestComponent($(this));
});
return test;
}
})(jQuery);
var test = $('#test').TestComponent();
test.sayHello();
console.log(test.name);
方法3、今天看了下jQueryUI的源码,它里面的扩展写法。
扩展初始化时会把生成的扩展实例保存起来作为element的属性;根据传递的参数判断是否是初始化扩展还是调用扩展的某方法。
(这也是为什么jQueryUI里都是方法,没有属性的原因了)
这样一方面可以链式调用jQuery对象的其他方面,另一方面无需再用变量存储组件实例。但是这种写法有点小恶心,回头想想有没有更好的方法,其实我觉得2的方式也未尝不可。(function($) {
var TestComponent = function(jqueryItem) {
this.item = jqueryItem;
this.item.each(function(){
console.log('init');
});
this._name = 'testName';
};
TestComponent.prototype.sayHello = function() {
console.log('hello');
};
TestComponent.prototype.name = function() {
return this._name;
};
$.fn.TestComponent = function(){
//init
if(arguments.length == 0) {
return this.each(function() {
this['componentInstance'] = new TestComponent($(this));
});
}
//else call function
else {
var methodName = arguments[0];
var rtn;
this.each(function(){
if(this['componentInstance']) {
rtn = this['componentInstance'][methodName]();
}
});
return rtn;
}
};
})(jQuery);
$('#test').TestComponent();
$('#test').TestComponent('sayHello');
console.log($('#test').TestComponent('name'));
以下摘录一些比较有名的jQuery插件的写法:
1、Magnific-Popup
$.fn.magnificPopup = function(options) {
_checkInstance();
var jqEl = $(this);
// We call some API method of first param is a string
if (typeof options === "string" ) {
if(options === 'open') {
var items,
itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup,
index = parseInt(arguments[1], 10) || 0;
if(itemOpts.items) {
items = itemOpts.items[index];
} else {
items = jqEl;
if(itemOpts.delegate) {
items = items.find(itemOpts.delegate);
}
items = items.eq( index );
}
mfp._openClick({mfpEl:items}, jqEl, itemOpts);
} else {
if(mfp.isOpen)
mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1));
}
} else {
/*
* As Zepto doesn't support .data() method for objects
* and it works only in normal browsers
* we assign "options" object directly to the DOM element. FTW!
*/
if(_isJQ) {
jqEl.data('magnificPopup', options);
} else {
jqEl[0].magnificPopup = options;
}
mfp.addGroup(jqEl, options);
}
return jqEl;
};
2、wookMark
$.fn.wookmark = function(options) {
// Create a wookmark instance if not available
if (!this.wookmarkInstance) {
this.wookmarkInstance = new Wookmark(this, options || {});
} else {
this.wookmarkInstance.update(options || {});
}
// Apply layout
this.wookmarkInstance.layout();
// Display items (if hidden) and return jQuery object to maintain chainability
return this.show();
};
以下代码摘录自jqueryUI
1、判断是否初始化还是调用方法
$.widget.bridge = function( name, object ) {
var fullName = object.prototype.widgetFullName || name;
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
args = slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.widget.extend.apply( null, [ options ].concat(args) ) :
options;
if ( isMethodCall ) {
this.each(function() {
var methodValue,
instance = $.data( this, fullName );
if ( !instance ) {
return $.error( "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'" );
}
if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
return $.error( "no such method '" + options + "' for " + name + " widget instance" );
}
methodValue = instance[ options ].apply( instance, args );
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue && methodValue.jquery ?
returnValue.pushStack( methodValue.get() ) :
methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data( this, fullName );
if ( instance ) {
instance.option( options || {} )._init();
} else {
$.data( this, fullName, new object( options, this ) );
}
});
}
return returnValue;
};
};
2、初始化时绑定实例到element上
$.Widget.prototype = {
widgetName: "widget",
widgetEventPrefix: "",
defaultElement: "<div>",
options: {
disabled: false,
// callbacks
create: null
},
_createWidget: function( options, element ) {
element = $( element || this.defaultElement || this )[ 0 ];
this.element = $( element );
this.uuid = uuid++;
this.eventNamespace = "." + this.widgetName + this.uuid;
this.options = $.widget.extend( {},
this.options,
this._getCreateOptions(),
options );
this.bindings = $();
this.hoverable = $();
this.focusable = $();
if ( element !== this ) {
$.data( element, this.widgetFullName, this );
this._on( true, this.element, {
remove: function( event ) {
if ( event.target === element ) {
this.destroy();
}
}
});
this.document = $( element.style ?
// element within the document
element.ownerDocument :
// element is window or document
element.document || element );
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
}
this._create();
this._trigger( "create", null, this._getCreateEventData() );
this._init();
}
3、通用的扩展写法
$.widget("ui.draggable", $.ui.mouse, {
version: "1.10.3",
widgetEventPrefix: "drag",
options: {
addClasses: true,
appendTo: "parent",
axis: false,
connectToSortable: false,
containment: false,
cursor: "auto",
cursorAt: false,
grid: false,
handle: false,
helper: "original",
iframeFix: false,
opacity: false,
refreshPositions: false,
revert: false,
revertDuration: 500,
scope: "default",
scroll: true,
scrollSensitivity: 20,
scrollSpeed: 20,
snap: false,
snapMode: "both",
snapTolerance: 20,
stack: false,
zIndex: false,
// callbacks
drag: null,
start: null,
stop: null
},
_create: function() {
if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
this.element[0].style.position = "relative";
}
if (this.options.addClasses){
this.element.addClass("ui-draggable");
}
if (this.options.disabled){
this.element.addClass("ui-draggable-disabled");
}
this._mouseInit();
},