zoukankan      html  css  js  c++  java
  • jQuery UI Widget 原理

    先看下代码的相关注释: 

    Javascript代码  
      1 /*! 
      2  * jQuery UI Widget 1.8.1 
      3  * 
      4  * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) 
      5  * Dual licensed under the MIT (MIT-LICENSE.txt) 
      6  * and GPL (GPL-LICENSE.txt) licenses. 
      7  * 
      8  * http://docs.jquery.com/UI/Widget 
      9  */  
     10 (function( $ ) {  
     11    
     12 var _remove = $.fn.remove;  
     13    
     14 $.fn.remove = function( selector, keepData ) {  
     15     return this.each(function() {  
     16         if ( !keepData ) {  
     17             if ( !selector || $.filter( selector, [ this ] ).length ) {  
     18                 $( "*", this ).add( this ).each(function() {  
     19                     $( this ).triggerHandler( "remove" );  
     20                 });  
     21             }  
     22         }  
     23         //dom元素在被删除前,触发一下remove事件,jquery框架本身没有对元素删除绑定事件  
     24         return _remove.call( $(this), selector, keepData );  
     25     });  
     26 };  
     27    
     28 $.widget = function( name, base, prototype ) {  
     29     var namespace = name.split( "." )[ 0 ],  
     30         fullName;  
     31     name = name.split( "." )[ 1 ];  
     32     fullName = namespace + "-" + name;  
     33     //比如ui.tab,上面的name='tab';fullName='ui-tab';  
     34    
     35     if ( !prototype ) {  
     36         prototype = base;  
     37         base = $.Widget;  
     38     }  
     39     //如果没有prototype,那么prototype就是base参数,实际base默认为$.Widget  
     40    
     41     // create selector for plugin  
     42     $.expr[ ":" ][ fullName ] = function( elem ) {  
     43         return !!$.data( elem, name );  
     44     };  
     45    
     46     $[ namespace ] = $[ namespace ] || {};//是否有命名空间  
     47     $[ namespace ][ name ] = function( options, element ) {//根据上面的例子,即初始化了$.ui.tab=func  
     48         // allow instantiation without initializing for simple inheritance  
     49         if ( arguments.length ) {  
     50             this._createWidget( options, element );  
     51         }  
     52     };  
     53    
     54     var basePrototype = new base();//初始化,一般都是调用了new $.Widget()  
     55     // we need to make the options hash a property directly on the new instance  
     56     // otherwise we'll modify the options hash on the prototype that we're  
     57     // inheriting from  
     58 //  $.each( basePrototype, function( key, val ) {  
     59 //      if ( $.isPlainObject(val) ) {  
     60 //          basePrototype[ key ] = $.extend( {}, val );  
     61 //      }  
     62 //  });  
     63     basePrototype.options = $.extend( {}, basePrototype.options );//初始化options值,注意不需要深度拷贝  
     64     $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {  
     65         namespace: namespace,  
     66         widgetName: name,  
     67         widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,  
     68         widgetBaseClass: fullName  
     69     }, prototype );  
     70     //为新的ui模块创建原型,使用深度拷贝,在basePrototype上扩展一些模块基本信息,在扩展prototype,比如ui.tabs.js中就是tab的拥有各种方法的大对象  
     71    
     72     $.widget.bridge( name, $[ namespace ][ name ] );//将此方法挂在jQuery对象上  
     73 };  
     74    
     75 $.widget.bridge = function( name, object ) {  
     76     $.fn[ name ] = function( options ) {  
     77         var isMethodCall = typeof options === "string",  
     78             args = Array.prototype.slice.call( arguments, 1 ),  
     79             returnValue = this;  
     80         //如果第一个参数是string类型,就认为是调用模块方法  
     81         //剩下的参数作为方法的参数,后面会用到  
     82    
     83         // allow multiple hashes to be passed on init  
     84         options = !isMethodCall && args.length ?  
     85             $.extend.apply( null, [ true, options ].concat(args) ) :  
     86             options;  
     87         //可以简单认为是$.extend(true,options,args[0],...),args可以是一个参数或是数组  
     88    
     89         // prevent calls to internal methods  
     90         if ( isMethodCall && options.substring( 0, 1 ) === "_" ) {  
     91             return returnValue;  
     92         }  
     93         //开头带下划线的方法都是私有方法,不让调用  
     94    
     95         if ( isMethodCall ) {//如果是调用函数  
     96             this.each(function() {  
     97                 var instance = $.data( this, name ),//得到实例,实例作为一个数据和元素关联上  
     98                     methodValue = instance && $.isFunction( instance[options] ) ?  
     99                         instance[ options ].apply( instance, args ) ://如果实例和方法均存在,调用方法,把args作为参数传进去  
    100                         instance;//否则返回undefined  
    101                 if ( methodValue !== instance && methodValue !== undefined ) {//如果methodValue不是jquery对象也不是undefined  
    102                     returnValue = methodValue;  
    103                     return false;//跳出each,一般获取options的值会走这个分支  
    104                 }  
    105             });  
    106         } else {//不是函数调用的话  
    107             this.each(function() {  
    108                 var instance = $.data( this, name );  
    109                 if ( instance ) {//实例存在  
    110                     if ( options ) {//有参数  
    111                         instance.option( options );//调用option函数,一般是设置状态之类的操作  
    112                     }  
    113                     instance._init();//再次调用此函数,根据options调整  
    114                 } else {  
    115                     $.data( this, name, new object( options, this ) );  
    116                     //没有实例的话,给元素绑定一个实例。注意这里的this是dom,object是模块类  
    117                 }  
    118             });  
    119         }  
    120    
    121         return returnValue;//返回,有可能是jquery对象,有可能是其他值  
    122     };  
    123 };  
    124    
    125 $.Widget = function( options, element ) {//所有模块的基类  
    126     // allow instantiation without initializing for simple inheritance  
    127     if ( arguments.length ) {//如果有参数,调用初始化函数  
    128         this._createWidget( options, element );  
    129     }  
    130 };  
    131    
    132 $.Widget.prototype = {  
    133     widgetName: "widget",  
    134     widgetEventPrefix: "",  
    135     options: {  
    136         disabled: false  
    137     },//上面的属性会在创建模块时被覆盖  
    138     _createWidget: function( options, element ) {  
    139         // $.widget.bridge stores the plugin instance, but we do it anyway  
    140         // so that it's stored even before the _create function runs  
    141         this.element = $( element ).data( this.widgetName, this );//缓存实例,保存jquery对象  
    142         this.options = $.extend( true, {},  
    143             this.options,  
    144             $.metadata && $.metadata.get( element )[ this.widgetName ],  
    145             options );//参数处理  
    146    
    147         var self = this;  
    148         this.element.bind( "remove." + this.widgetName, function() {  
    149             self.destroy();  
    150         });//注册销毁事件  
    151    
    152         this._create();//创建  
    153         this._init();//初始化  
    154     },  
    155     _create: function() {},  
    156     _init: function() {},  
    157    
    158     destroy: function() {//销毁模块:去除绑定事件、去除数据、去除样式、属性  
    159         this.element  
    160             .unbind( "." + this.widgetName )  
    161             .removeData( this.widgetName );  
    162         this.widget()  
    163             .unbind( "." + this.widgetName )  
    164             .removeAttr( "aria-disabled" )  
    165             .removeClass(  
    166                 this.widgetBaseClass + "-disabled " +  
    167                 "ui-state-disabled" );  
    168     },  
    169    
    170     widget: function() {//返回jquery对象  
    171         return this.element;  
    172     },  
    173    
    174     option: function( key, value ) {//设置选项函数  
    175         var options = key,  
    176             self = this;  
    177    
    178         if ( arguments.length === 0 ) {  
    179             // don't return a reference to the internal hash  
    180             return $.extend( {}, self.options );//返回一个新的对象,不是内部数据的引用  
    181         }  
    182    
    183         if  (typeof key === "string" ) {  
    184             if ( value === undefined ) {  
    185                 return this.options[ key ];//取值  
    186             }  
    187             options = {};  
    188             options[ key ] = value;//设置值  
    189         }  
    190    
    191         $.each( options, function( key, value ) {  
    192             self._setOption( key, value );//调用内部的_setOption  
    193         });  
    194    
    195         return self;  
    196     },  
    197     _setOption: function( key, value ) {  
    198         this.options[ key ] = value;  
    199    
    200         if ( key === "disabled" ) {//增加或是去除className  
    201             this.widget()  
    202                 [ value ? "addClass" : "removeClass"](  
    203                     this.widgetBaseClass + "-disabled" + " " +  
    204                     "ui-state-disabled" )  
    205                 .attr( "aria-disabled", value );  
    206         }  
    207    
    208         return this;  
    209     },  
    210    
    211     enable: function() {  
    212         return this._setOption( "disabled", false );  
    213     },  
    214     disable: function() {  
    215         return this._setOption( "disabled", true );  
    216     },  
    217    
    218     _trigger: function( type, event, data ) {  
    219         var callback = this.options[ type ];  
    220    
    221         event = $.Event( event );  
    222         event.type = ( type === this.widgetEventPrefix ?  
    223             type :  
    224             this.widgetEventPrefix + type ).toLowerCase();  
    225         data = data || {};  
    226    
    227         // copy original event properties over to the new event  
    228         // this would happen if we could call $.event.fix instead of $.Event  
    229         // but we don't have a way to force an event to be fixed multiple times  
    230         if ( event.originalEvent ) {//把原始的event属性重新赋到event变量上  
    231             for ( var i = $.event.props.length, prop; i; ) {  
    232                 prop = $.event.props[ --i ];  
    233                 event[ prop ] = event.originalEvent[ prop ];  
    234             }  
    235         }  
    236    
    237         this.element.trigger( event, data );  
    238    
    239         return !( $.isFunction(callback) &&  
    240             callback.call( this.element[0], event, data ) === false ||  
    241             event.isDefaultPrevented() );  
    242     }  
    243 };  
    244    
    245 })( jQuery );  


    上面是jquery.ui.widget.js的源码,jquery ui的所有模块都是基于其中的widget方法进行扩展,使用统一的命名规范和编码风格。 
    先来说一下原理: 
    $.widget此函数完成了对jQuery本身的扩展,根据第一个参数来确定模块的命名空间和函数名;第二个参数确定模块的基类(默认是$.Widget);第三个参数实现模块本身的方法。比如标签切换插件jquery.ui.tabs.js中开始: 
    $.widget(“ui.tabs”, {…});//这里只有两个参数,那么基类就默认是$.Widget 
    第一个参数:”ui.tabs”用来表示在jQuery上选择(或增加)一个命名空间,即如果jQuery.ui不存在,则定义jQuery.ui = {},然后在jQuery.ui上增加一个函数,名称为tabs.最后调用$.widget.bridge将tabs方法挂在jQuery对象上。这样,所有的jquery对象将拥有tabs方法。 

    注意:jquery ui有严格的命名规范,每个控件对外只暴露一个借口。控件所有方法或属性通过向此借口传递不同参数来调用和获取。 

    jquery ui的大部分控件是基于$.Widget基类实现的。所以一般我们做控件是都要重写$.Widget类中的一些方法。一般来说,一个ui控件需要实现下列的方法或属性: 
    属性: 
    options 用来缓存控件各项参数 
    私有方法,使用“$(xx).tabs(私有方法)”这种方式来调用私有方法时会立刻返回,调用不能成功: 
    _create 控件初始化调用,多次调用$(xx).tabs()这样不带参数的方法只会执行一次 
    _init 一般不用实现,默认为空函数,每次“$(xx).tabs()”这样调用时会调用此方法 
    _setOption “$(xx).tabs(‘option’,xxx)”这种调用方式会调用此方法 
    公开方法: 
    destroy 销毁模块 
    option 设置或获取参数 
    enable 启用模块功能 
    disable 禁用功能 

    几乎所有的jquery ui控件都会重写这些接口,同时增加控件相关的私有或公有方法。 

    记住,jquery ui的实例是和元素关联起来的,作为数据保存起来了。暴露给用户使用的只是jquery对象上增加的方法。一般我们不需要获取ui的实例。

  • 相关阅读:
    Linux下动态库(.so)和静态库(.a) 的区别
    CTS、CLS、CLR
    ASP.NET简介及网页基础知识
    ASP.NET MVC中ActionResult的不同返回方式
    ADO.NET中的数据库帮助类
    ASP.NET MVC 方法View返回的两种方式
    使用win10 IIS 发布局域网网站
    ASP.NET MVC 给Action的参数赋值的方式
    ASP.NET MVC简单流程解释(传值方式)
    ASP.NET MVC 简介(附VS2019和VSCode版示例)
  • 原文地址:https://www.cnblogs.com/opps/p/4011390.html
Copyright © 2011-2022 走看看