jquery ui widget原理解析

来源:互联网 发布:实时网速监控软件 编辑:程序博客网 时间:2024/05/22 05:31

jquery ui widget提供了对jquery ui的widget进行创建和使用的基本运行框架和接口规范。其主要作用是对定义的widget组件提供组件注册和继承机制的功能。

在jquery ui widget主要提供了$.widget和$.Widget两个函数对象。一个小写一个大写。

$.widget提供了对widget组件类的构造函数的创建,实现对widget类的完整属性的封装和注册功能以及继承机制。查看jquery ui的源码可以看到,jquery ui中的所有自定义widget组件都是使用$.widget函数创建的,使用$.widget函数就会生成该widget组件类的构造函数,然后使用$.widget.bridge函数来将自定义的widget组件类注册到jQuery对象上,然后在jQuery对象上就可以使用该方法了,可以使用jQuery(element).xxWidget()的方法进行调用了,其中xxWidget就是widget的name,这样就完成了对jQuery对象的扩展。一个自定义的widget类利用$.widget函数只需要实现特定的接口方法和指定widget自己私有和公有属性和方法后即可。$.widget负责对于这些函数的调用以及属性的继承机制的实现。至于需要实现哪些接口方法,这些就是$.Widget(注意是大写的W)函数对象的作用了。

$.Widget是所有jquery ui中widget组件的基类,就和javascript所有对象的基类是Object一样。$.Widget里面定义了jquery ui中定义的组件的一些基本属性和接口方法。例如里面有一些基本的属性有:

widgetName: "widget",widgetEventPrefix: "",defaultElement: "<div>",options: {disabled: false,// callbackscreate: null}
jquery ui中每个定义的widget中都有这些属性,也可以进行重写。

还有一些定义的widget需要实现的方法,包括:_getCreateOptions, _getCreateEventData, _create, _init, _destroy等。当然这里面有些函数可以不实现,一般情况下,_create函数是必须实现的,其余的函数可以根据需要而定。

好了,基本的原理就是这样。下面分别对$..widget和$.Widget的源码进行解析。这里以jQuery UI Widget 1.11.4版本为例。

首先看一下$..widget。

$.widget = function( name, base, prototype ) {var fullName, existingConstructor, constructor, basePrototype,// proxiedPrototype allows the provided prototype to remain unmodified// so that it can be used as a mixin for multiple widgets (#8876)proxiedPrototype = {},//一般的widget在定义的时候,都是xxx.xxxx的形式,分别表示namespace和widgetName//例如ui.tab,namespace='ui';name='tab';fullName='ui-tab';namespace = name.split( "." )[ 0 ];name = name.split( "." )[ 1 ];fullName = namespace + "-" + name;//如果prototype没有指定,那么就以$.Widget作为基类if ( !prototype ) {prototype = base;base = $.Widget;}//$.expr.pseudos === $.expr[":"] 用来创建自定义的伪类选择器,可以直接使用fullName选择出已经创建该plugin的element//关于$.expr.pseudos原理请见http://www.tuicool.com/articles/NzYRZvq// create selector for plugin$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {return !!$.data( elem, fullName );};$[ namespace ] = $[ namespace ] || {};existingConstructor = $[ namespace ][ name ];constructor = $[ namespace ][ name ] = function( options, element ) {//加这句代码的意思就是允许不用new constructor()的形式,而是可以用constructor()直接创建plugin的对象//因为$.widget函数返回的就是constructor函数//因为直接用constructor()创建的话,this就可能不是当前正在创建的对象,例如可能是window对象。这样this上就不会有createWidget函数//因此在这里面重新调用new constructor()函数来将this指针指向为当前的对象//因为所有的jquery ui的widget都是继承$.Widget,所以此时的this肯定就会有createWidget了,所以不会死循环//对于javascript new的原理可以参考http://www.cnblogs.com/purediy/archive/2012/09/12/2682490.html//以及javascript原型原理http://www.cnblogs.com/wilber2013/p/4924309.html// allow instantiation without "new" keywordif ( !this._createWidget ) {return new constructor( options, element );}// allow instantiation without initializing for simple inheritance// must use "new" keyword (the code above always passes args)if ( arguments.length ) {this._createWidget( options, element );}};//也许之前已经定义过该plugin,这里将已有的构造函数的属性拷贝到现在新的构造函数上// extend with the existing constructor to carry over any static properties$.extend( constructor, existingConstructor, {version: prototype.version,// copy the object used to create the prototype in case we need to// redefine the widget later_proto: $.extend( {}, prototype ),// track widgets that inherit from this widget in case this widget is// redefined after a widget inherits from it_childConstructors: []});//创建父类的空对象,这样父类中所有的属性都是默认值,因而可以实现属性的继承basePrototype = new base();// we need to make the options hash a property directly on the new instance// otherwise we'll modify the options hash on the prototype that we're// inheriting frombasePrototype.options = $.widget.extend( {}, basePrototype.options );$.each( prototype, function( prop, value ) {if ( !$.isFunction( value ) ) {proxiedPrototype[ prop ] = value;return;}//对该widget内部定义的方法都进行了代理类的封装proxiedPrototype[ prop ] = (function() {var _super = function() {return base.prototype[ prop ].apply( this, arguments );},_superApply = function( args ) {return base.prototype[ prop ].apply( this, args );};return function() {var __super = this._super,__superApply = this._superApply,returnValue;this._super = _super;this._superApply = _superApply;returnValue = value.apply( this, arguments );this._super = __super;this._superApply = __superApply;return returnValue;};})();});constructor.prototype = $.widget.extend( basePrototype, {// TODO: remove support for widgetEventPrefix// always use the name + a colon as the prefix, e.g., draggable:start// don't prefix for widgets that aren't DOM-based//widgetEventPrefix为name 这样该widget的所有事件就会widgetEventPrefix+event,//使用bind监听该widget的事件时,需要bind(widgetEventPrefix+event...)widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name  }, proxiedPrototype, {constructor: constructor,  //每个函数的原型都需要有constructor属性来指向构造函数本身namespace: namespace,widgetName: name,widgetFullName: fullName});//在重新定义了父类的构造函数后,所有继承该类的子类的构造函数都要重新定义// If this widget is being redefined then we need to find all widgets that// are inheriting from it and redefine all of them so that they inherit from// the new version of this widget. We're essentially trying to replace one// level in the prototype chain.if ( existingConstructor ) {$.each( existingConstructor._childConstructors, function( i, child ) {var childPrototype = child.prototype;// redefine the child widget using the same prototype that was// originally used, but inherit from the new version of the base$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );});// remove the list of existing child constructors from the old constructor// so the old child constructors can be garbage collecteddelete existingConstructor._childConstructors;} else {base._childConstructors.push( constructor );}//将自定义的plugin注册到jQuery对象上,这样就可以使用jQuery创建对象的方式进行plugin对象的创建$.widget.bridge( name, constructor );return constructor;};

这里面用到的bridge函数正如上面所说,将widget注册在jQuery对象上,在bridge函数内部创建了$.fn[name]函数。函数源码如下:

$.widget.bridge = function( name, object ) {var fullName = object.prototype.widgetFullName || name;//使用widget的name将widget注册在jQuery对象上$.fn[ name ] = function( options ) {var isMethodCall = typeof options === "string",args = widget_slice.call( arguments, 1 ),//returnValue首先赋值为s,即当前的jQuery实例对象,是为了统一jquery的链式操作returnValue = this;//如果传进来的参数是string,说明是调用widget内部的指定的函数if ( isMethodCall ) {this.each(function() {var methodValue,instance = $.data( this, fullName );//如果传进来的参数是"instance",直接返回在该元素上创建的该widget实例if ( options === "instance" ) {returnValue = instance;return false;}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 ?  //说明methodValue是一个JQuery实例对象//使用pushStack重新把returnValue变成一个jQuery实例对象,//methodValue本来就是个jQuery对象,为什么不直接把returnValue直接赋值给returnValue呢//这是因为链式操作中,可能methodValue的prevObject不是当前的this对象,//这样如果进行end()操作就不会回退到当前的this jQuery实例对象,破坏了操作的延续性//而pushStack的作用就是将传入的dom元素集合压入一个新的JQuery对象实例中,//并把这个新的JQuery对象实例的prevObject赋值为pushStack函数的调用者returnValue.pushStack( methodValue.get() ) : //methodValue.get()返回dom元素数组methodValue;return false;}});} else {// Allow multiple hashes to be passed on initif ( args.length ) {options = $.widget.extend.apply( null, [ options ].concat(args) );}this.each(function() {var instance = $.data( this, fullName );//如果该元素上已经创建该widget实例对象,就重新设置option参数,并调用_init函数if ( instance ) {instance.option( options || {} );if ( instance._init ) {instance._init();}} else {//调用构造函数创建该widget的实例对象,并且把对象存入该dom元素上$.data( this, fullName, new object( options, this ) );}});}return returnValue;};};

好了,$.widget的原理基本上解析完了,下面再来看看jquery ui所有widget的基类$.Widget

//空的构造函数$.Widget = function( /* options, element */ ) {};//用于记录继承该类的子类$.Widget._childConstructors = [];//每个函数对象都有原型对象,可以实现继承机制//这里面包括了widget所具有的一些基本属性和方法。所有的jquery ui widget都会拥有这些属性和方法$.Widget.prototype = {widgetName: "widget",widgetEventPrefix: "",defaultElement: "<div>",options: {disabled: false,// callbackscreate: null},//真正的初始化方法,每个widget都会调用该方法来执行初始化创建_createWidget: function( options, element ) {element = $( element || this.defaultElement || this )[ 0 ];this.element = $( element );this.uuid = widget_uuid++;this.eventNamespace = "." + this.widgetName + this.uuid;this.bindings = $();this.hoverable = $();this.focusable = $();if ( element !== this ) {$.data( element, this.widgetFullName, this );//使用Widget的_on函数注册remove事件处理函数,第一个参数为true,表示不禁用该事件响应函数this._on( true, this.element, {remove: function( event ) {if ( event.target === element ) {this.destroy();}}});this.document = $( element.style ?// element within the documentelement.ownerDocument :// element is window or documentelement.document || element );this.window = $( this.document[0].defaultView || this.document[0].parentWindow );}this.options = $.widget.extend( {},this.options,this._getCreateOptions(),options );//这里调用的_create函数执行具体的初始化创建操作this._create();this._trigger( "create", null, this._getCreateEventData() );this._init();},_getCreateOptions: $.noop,_getCreateEventData: $.noop,_create: $.noop,_init: $.noop,destroy: function() {this._destroy();// we can probably remove the unbind calls in 2.0// all event bindings should go through this._on()this.element.unbind( this.eventNamespace ).removeData( this.widgetFullName )// support: jquery <1.6.3// http://bugs.jquery.com/ticket/9413.removeData( $.camelCase( this.widgetFullName ) );this.widget().unbind( this.eventNamespace ).removeAttr( "aria-disabled" ).removeClass(this.widgetFullName + "-disabled " +"ui-state-disabled" );// clean up events and statesthis.bindings.unbind( this.eventNamespace );this.hoverable.removeClass( "ui-state-hover" );this.focusable.removeClass( "ui-state-focus" );},_destroy: $.noop,widget: function() {return this.element;},//获取或设置特定的option value.支持使用“xx.xxx”的形式来操作深层次的对象option: function( key, value ) {var options = key,parts,curOption,i;//直接调用option(),不指定参数,就直接返回该widget的options对象if ( arguments.length === 0 ) {// don't return a reference to the internal hashreturn $.widget.extend( {}, this.options );}if ( typeof key === "string" ) {// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }options = {};parts = key.split( "." );key = parts.shift();if ( parts.length ) {curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );for ( i = 0; i < parts.length - 1; i++ ) {curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};curOption = curOption[ parts[ i ] ];}key = parts.pop();if ( arguments.length === 1 ) {return curOption[ key ] === undefined ? null : curOption[ key ];}curOption[ key ] = value;} else {if ( arguments.length === 1 ) {return this.options[ key ] === undefined ? null : this.options[ key ];}options[ key ] = value;}}this._setOptions( options );return this;},_setOptions: function( options ) {var key;for ( key in options ) {this._setOption( key, options[ key ] );}return this;},_setOption: function( key, value ) {this.options[ key ] = value;if ( key === "disabled" ) {this.widget().toggleClass( this.widgetFullName + "-disabled", !!value );// If the widget is becoming disabled, then nothing is interactiveif ( value ) {this.hoverable.removeClass( "ui-state-hover" );this.focusable.removeClass( "ui-state-focus" );}}return this;},enable: function() {return this._setOptions({ disabled: false });},disable: function() {return this._setOptions({ disabled: true });},//事件监听注册函数 suppressDisabledCheck表示是否禁止该事件禁用的检查_on: function( suppressDisabledCheck, element, handlers ) {var delegateElement,instance = this;// no suppressDisabledCheck flag, shuffle argumentsif ( typeof suppressDisabledCheck !== "boolean" ) {handlers = element;element = suppressDisabledCheck;suppressDisabledCheck = false;}// no element argument, shuffle and use this.elementif ( !handlers ) {handlers = element;element = this.element;delegateElement = this.widget();} else {element = delegateElement = $( element );this.bindings = this.bindings.add( element );}$.each( handlers, function( event, handler ) {function handlerProxy() {// allow widgets to customize the disabled handling// - disabled as an array instead of boolean// - disabled class as method for disabling individual partsif ( !suppressDisabledCheck && //如果对该事件的禁用与否进行检查且该instance的disable为true,那么就不会响应该事件,响应函数不起作用( instance.options.disabled === true ||$( this ).hasClass( "ui-state-disabled" ) ) ) {return;}return ( typeof handler === "string" ? instance[ handler ] : handler ).apply( instance, arguments );}// copy the guid so direct unbinding worksif ( typeof handler !== "string" ) {handlerProxy.guid = handler.guid =handler.guid || handlerProxy.guid || $.guid++;}//对事件监听函数进行绑定。传进来的event参数可能是“eventName selector”的形式var match = event.match( /^([\w:-]*)\s*(.*)$/ ),eventName = match[1] + instance.eventNamespace,selector = match[2];if ( selector ) {//如果有selector则表示对delegateElement的某子元素上的事件进行监听并注册事件响应函数delegateElement.delegate( selector, eventName, handlerProxy );} else {element.bind( eventName, handlerProxy );}});},//取消事件的监听_off: function( element, eventName ) {eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +this.eventNamespace;element.unbind( eventName ).undelegate( eventName );// Clear the stack to avoid memory leaks (#10056)this.bindings = $( this.bindings.not( element ).get() );this.focusable = $( this.focusable.not( element ).get() );this.hoverable = $( this.hoverable.not( element ).get() );},//延迟调用函数_delay: function( handler, delay ) {function handlerProxy() {return ( typeof handler === "string" ? instance[ handler ] : handler ).apply( instance, arguments );}var instance = this;return setTimeout( handlerProxy, delay || 0 );},//增加鼠标悬停事件响应函数_hoverable: function( element ) {this.hoverable = this.hoverable.add( element );this._on( element, {mouseenter: function( event ) {$( event.currentTarget ).addClass( "ui-state-hover" );},mouseleave: function( event ) {$( event.currentTarget ).removeClass( "ui-state-hover" );}});},//增加焦点事件响应函数_focusable: function( element ) {this.focusable = this.focusable.add( element );this._on( element, {focusin: function( event ) {$( event.currentTarget ).addClass( "ui-state-focus" );},focusout: function( event ) {$( event.currentTarget ).removeClass( "ui-state-focus" );}});},//触发事件函数 type表示事件的类型,event表示具体的事件对象,可为空_trigger: function( type, event, data ) {var prop, orig,callback = this.options[ type ]; //直接获取该类型的事件的回调函数data = data || {};event = $.Event( event );event.type = ( type === this.widgetEventPrefix ?type :this.widgetEventPrefix + type ).toLowerCase();// the original event may come from any element// so we need to reset the target on the new eventevent.target = this.element[ 0 ];// copy original event properties over to the new eventorig = event.originalEvent;if ( orig ) {for ( prop in orig ) {if ( !( prop in event ) ) {event[ prop ] = orig[ prop ];}}}this.element.trigger( event, data );   //触发事件return !( $.isFunction( callback ) &&callback.apply( this.element[0], [ event ].concat( data ) ) === false ||event.isDefaultPrevented() );}};



0 0