zoukankan      html  css  js  c++  java
  • jquery事件核心源码分析

         我们从绑定事件开始,一步步往下看:

         以jquery.1.8.3为例,平时通过jquery绑定事件最常用的是on方法,大概分为下面3种类型:

    $(target).on('click',function(){
        //函数内容    
    })
    
    $(target).on('click','.child',function(){
        //函数内容    
    })
    
    $(target).on({
        click:function(){},
        mouseover:function(){},
        mouseout:function(){}
    })

        第一种是我们最常用的写法,通过元素选择器,直接绑定事件;

        第二种则是利用了事件委托原理,由最初的父元素代理子元素的事件,动态添加的元素绑定事件用第一种方法时无效的;

        第三种则是同一元素同时绑定多个事件时的简略写法。

        我们来看一下on方法的源码,如果我们想封装插件类似on方法调用,可以像on一样来书写,具体可见另一篇文章jQuery插件开发(溢出滚动)

    on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
            var origFn, type;
    
            // Types can be a map of types/handlers
            //上述第三种用法,传入第一个参数为object
            if ( typeof types === "object" ) {
                // ( types-Object, selector, data )
                if ( typeof selector !== "string" ) { // && selector != null
                    // ( types-Object, data )
                    data = data || selector;
                    selector = undefined;
                }
                for ( type in types ) {
                    this.on( type, selector, data, types[ type ], one );
                }
                return this;
            }
    
            if ( data == null && fn == null ) {
                // ( types, fn )
                fn = selector;
                data = selector = undefined;
            } else if ( fn == null ) {
                if ( typeof selector === "string" ) {
                    // ( types, selector, fn )
                    fn = data;
                    data = undefined;
                } else {
                    // ( types, data, fn )
                    fn = data;
                    data = selector;
                    selector = undefined;
                }
            }
            if ( fn === false ) {
                fn = returnFalse;
            } else if ( !fn ) {
                return this;
            }
    
            if ( one === 1 ) {
                origFn = fn;
                fn = function( event ) {
                    // Can use an empty set, since event contains the info
                    jQuery().off( event );
                    return origFn.apply( this, arguments );
                };
                // Use same guid so caller can remove using origFn
                fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
            }
            return this.each( function() {
                jQuery.event.add( this, types, fn, data, selector );
            });
        }

         我们可以看到,on方法内部的代码类似于初始化,通过对传入参数的分析,来矫正type,fn,data,selector等变量,从而正确的调用jquery.event.add方法。jquery.event是事件的核心。

        jquery.event 代码结构如下:

    jQuery.event = {
           add:function(){
               
           },
           global:{},
           remove:function(){
               
           },
           customEvent:function(){
               
           },
           trigger:function(){
               
           },
           dispatch:function(){
              //在老版本的jquery,此方法名为handle 
           },
           props:'',
           fixHooks:{
               
           },
           keyHooks    :{
               
           },
           mouseHooks:{
               
           },
           fix:function(){
               
           },
           special:function(){
               
               
           },
           simulate :function(){
               
               
           }
    }

          其中add方法通过一些设置为元素注册添加事件:

          所谓的特殊事件指类似于mouseenter,mouseleave,ready事件并不是浏览器所支持的事件,他们不能通过统一的addEventListener/attachEvent来添加这个事件.而是通过setup和teardown来绑定和删除事件,如下:

    beforeunload: {
        setup: function( data, namespaces, eventHandle ) {
            // We only want to do this special case on windows
            if ( jQuery.isWindow( this ) ) {
                this.onbeforeunload = eventHandle;
            }
        },
    
        teardown: function( namespaces, eventHandle ) {
            if ( this.onbeforeunload === eventHandle ) {
                this.onbeforeunload = null;
            }
        }
    }
    add: function( elem, types, handler, data, selector ) {
    
      var elemData, eventHandle, events,
          t, tns, type, namespaces, handleObj,
          handleObjIn, handlers, special;
    
      // Don't attach events to noData or text/comment nodes (allow plain objects tho)
      if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
          return;
      }
      
      // Caller can pass in an object of custom data in lieu of the handler
      // 如果传入的handler包括handler属性,则通过临时变量将handler与selector设置为正确的指向。
      if ( handler.handler ) {
          handleObjIn = handler;
          handler = handleObjIn.handler;
          selector = handleObjIn.selector;
      }
    
      // Make sure that the handler has a unique ID, used to find/remove it later
      //为每个元素添加一个唯一的guid
      if ( !handler.guid ) {
          handler.guid = jQuery.guid++;  
      }
    
      // Init the element's event structure and main handler, if this is the first
      //elemData结构见下面截图
      events = elemData.events;
      if ( !events ) {
          elemData.events = events = {};  //初次绑定事件
      }
      eventHandle = elemData.handle;
      if ( !eventHandle ) {
          //eventHandle 经过dispatch处理,已不同于最初传入的handler
          elemData.handle = eventHandle = function( e ) {
              // Discard the second event of a jQuery.event.trigger() and
              // when an event is called after a page has unloaded
              return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
                  jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
                  undefined;
          };
          // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
          eventHandle.elem = elem;
      }
    
      // Handle multiple events separated by a space
      // jQuery(...).bind("mouseover mouseout", fn);
      types = jQuery.trim( hoverHack(types) ).split( " " );
      for ( t = 0; t < types.length; t++ ) {  //类似 'click input keyUp'一次传入多个事件
    
          tns = rtypenamespace.exec( types[t] ) || [];
          type = tns[1];
          namespaces = ( tns[2] || "" ).split( "." ).sort();
    
          // If event changes its type, use the special event handlers for the changed type
          special = jQuery.event.special[ type ] || {};
    
          // If selector defined, determine special event api type, otherwise given type
          type = ( selector ? special.delegateType : special.bindType ) || type;
    
          // Update special based on newly reset type
          special = jQuery.event.special[ type ] || {};
    
          // handleObj is passed to all event handlers
          handleObj = jQuery.extend({
              type: type,
              origType: tns[1],
              data: data,
              handler: handler,
              guid: handler.guid,
              selector: selector,
              needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
              namespace: namespaces.join(".")
          }, handleObjIn );
    
          // Init the event handler queue if we're the first
          handlers = events[ type ];
          if ( !handlers ) {
              handlers = events[ type ] = [];
              handlers.delegateCount = 0;
    
              // Only use addEventListener/attachEvent if the special events handler returns false
              //如果为非special事件则由addeventListener或attachEvent事件绑定,否则择优special.setup绑定
              if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
                  // Bind the global event handler to the element
                  //当前eventHandle是经过处理的eventHandle
                  if ( elem.addEventListener ) {
                      elem.addEventListener( type, eventHandle, false );
    
                  } else if ( elem.attachEvent ) {
                      elem.attachEvent( "on" + type, eventHandle );
                  }
              }
          }
    
          if ( special.add ) {
              special.add.call( elem, handleObj );
    
              if ( !handleObj.handler.guid ) {
                  handleObj.handler.guid = handler.guid;
              }
          }
          // Add to the element's handler list, delegates in front
          if ( selector ) {  //元素事件为事件委托
              handlers.splice( handlers.delegateCount++, 0, handleObj );
          } else {           //绑定于元素本身的事件
              handlers.push( handleObj );
          }
          console.log(elemData)
          // Keep track of which events have ever been used, for event optimization
          jQuery.event.global[ type ] = true;
      }
    
      // Nullify elem to prevent memory leaks in IE
      elem = null;
    },

            其中注意 elemData = jQuery._data( elem )  这句,我们简单绑定一个事件,然后看elemData值

    $(document).click(function(){
        console.log(1)
    })

     

         如上左图,最终结果elemData即jquery的缓存数据中主要包含两个属性,events及handle,其中events包含了当前元素注册的所有事件,如click,keydown等,其中每一个事件下面又可以包括多个handler,每个handler有一个唯一的guid,后面触发及删除相应事件函数都要用到这个,events对象还包含一个属性为delegateCount,则记录着该元素总共代理事件的次数。在右图中可以看到在某一个事件下绑定的不同handler,代理事件(selector部位undefined的情况)排在前面,而绑定在元素自身的事件排在代理事件后面。

         需要注意的是代码中的elem.addEventListener( type, eventHandle, false )并不同于我们简单的将handler处理函数绑定,而是对handler通过dispatch进行了处理。

         另外,在事件函数中,js默认传入的第一个参数为事件对象.

         下面我们来看dispatch方法,该方法接受传入的event参数,并对绑定在元素上的事件进行处理:例如我们代码如下

    <div class='parent' style='900px;height:500px;background-color:#CCC'>
       <p class='child'>
         <a class='inner'>点击</a>
       </p>
    </div>
    <script>
    $('.parent').on('click',function(){
        
    })
    $('.parent').on('click','.child',function(){
        
    })
    $('.parent').on('click','.inner',function(){
        
    })
    </script>

         可以看到,div元素本身绑定有click事件,同时又代理子元素p和a的事件,这样当在div发生点击事件的时候,第一步dispatch会从事件元素的currentTarget开始往上循环遍历直到div元素,将需要触发事件的元素及事件加到handlerQueue数组中 (前提是元素本身有代理事件),  然后会将绑定在元素本身的事件添加到handlerQueue。经过上面两步的处理,handlerQueue就形成一个需要触发事件的集合,通过这个集合,我们便能正确的响应事件。

    dispatch: function( event ) {
        // Make a writable jQuery.Event from the native event object
        // 通过fix方法对event进行兼容性处理
        event = jQuery.event.fix( event || window.event );
    
        var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
            handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
            delegateCount = handlers.delegateCount,
            args = core_slice.call( arguments ),
            run_all = !event.exclusive && !event.namespace,
            special = jQuery.event.special[ event.type ] || {},
            handlerQueue = [];
    
        // Use the fix-ed jQuery.Event rather than the (read-only) native event
        args[0] = event;
        event.delegateTarget = this;
    
        // Call the preDispatch hook for the mapped type, and let it bail if desired
        if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
            return;
        }
    
        // Determine handlers that should run if there are delegated events
        // Avoid non-left-click bubbling in Firefox (#3861)
        
        
        //火狐右键会触发click事件,但是event.button值为2
        //delegateCount不为0代表元素本身有代理其他元素事件
        if ( delegateCount && !(event.button && event.type === "click") ) {
            /*事件从event.target冒泡到当前元素
            #  例如元素本身绑定有事件a,而且代理其子元素child事件b及child子元素c事件,
            #  则点击c元素时,执行事件顺序为c- b- a,即节点层次越深,事件执行优先级越高
            */
            for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
    
                // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
                if ( cur.disabled !== true || event.type !== "click" ) {
                    selMatch = {};
                    matches = [];
                    //代理事件,delegateCount为代理事件的数量,不同handler事件的顺序见上图中右图,代理事件在上,自身事件在下
                    for ( i = 0; i < delegateCount; i++ ) {  
                        handleObj = handlers[ i ];
                        sel = handleObj.selector;
                        if ( selMatch[ sel ] === undefined ) {
                            selMatch[ sel ] = handleObj.needsContext ?
                                jQuery( sel, this ).index( cur ) >= 0 :
                                jQuery.find( sel, this, null, [ cur ] ).length;
                        }
                        if ( selMatch[ sel ] ) {
                            matches.push( handleObj );
                        }
                    }
                    if ( matches.length ) {
                        handlerQueue.push({ elem: cur, matches: matches });  //委托事件
                    }
                }
            }
        }
    
        // Add the remaining (directly-bound) handlers
        if ( handlers.length > delegateCount ) {
            //自身事件
            handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
        }
    
        // Run delegates first; they may want to stop propagation beneath us
        // hangdlerQueue是一个集合元素自身事件及代理子元素事件的数组
        // 例如 html结构为 <div><p><a></a></p></div>,当点击范围在p同时不在a内时,则会执行p和div的事件,
        // 相对应的handlerQuesu中并不包含a
        for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
            matched = handlerQueue[ i ];
            event.currentTarget = matched.elem;
    
            for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
                handleObj = matched.matches[ j ];
    
                // Triggered event must either 1) be non-exclusive and have no namespace, or
                // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
                if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
    
                    event.data = handleObj.data;
                    event.handleObj = handleObj;
                    ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
                            .apply( matched.elem, args );
    
                    if ( ret !== undefined ) {
                        event.result = ret;
                        if ( ret === false ) {
                            event.preventDefault();
                            event.stopPropagation();
                        }
                    }
                }
            }
        }
    
        // Call the postDispatch hook for the mapped type
        if ( special.postDispatch ) {
            special.postDispatch.call( this, event );
        }
    
        return event.result;
    },

         

          具体如上所示,源码都做了相应备注,其中handlerQueue结构如下,前两项为代理事件,最后一项为元素本身事件,matches为当前元素handler集合。

          其中fix函数用于对事件对象的修正,首先构建一个新的可扩展的event对象,在jquery.event中还包含props,fixHooks,keyHooks,mouseHooks,分别存储了事件对象的公共属性,键盘事件属性,鼠标事件属性等,根据事件类型为新构建event对象赋予新的属性,同时我们在后期扩展时也可为该event对象赋予自定义属性。

    fix: function( event ) {
        if ( event[ jQuery.expando ] ) {
            return event;
        }
    
        // Create a writable copy of the event object and normalize some properties
        var i, prop,
            originalEvent = event,
            fixHook = jQuery.event.fixHooks[ event.type ] || {},
            copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
    
        event = jQuery.Event( originalEvent );
    
        for ( i = copy.length; i; ) {
            prop = copy[ --i ];
            event[ prop ] = originalEvent[ prop ];
        }
    
        // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
        if ( !event.target ) {
            event.target = originalEvent.srcElement || document;
        }
    
        // Target should not be a text node (#504, Safari)
        if ( event.target.nodeType === 3 ) {
            event.target = event.target.parentNode;
        }
    
        // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
        event.metaKey = !!event.metaKey;
    
        return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
    },

        

         当然jquery.event还有trigger,remove,simulate等其他方法,在此就不一一列举,基本思路都是一致的。对以上原理理解透了,就可以自己根据需要来扩展jquery方法,如mousewheel事件,我们可以利用fix方法来完成对event对象的扩展,而不用自己重新写一套兼容性的代码,具体下节再分析。

         文中如有错误及不当之处,请及时指出,谢谢! 

         文中所用jquery版本为1.8.3。1.2.6版本的jquery事件核心更容易理解。当然里面缺少事件代理的处理。

  • 相关阅读:
    完善例题3.2的日期类mydate
    杨辉三角形
    求100以内的素数
    九九乘法表
    实现计算机界面
    完善3.2例题
    杨辉三角法
    素数程序
    九九乘法表
    杨辉三角
  • 原文地址:https://www.cnblogs.com/mopagunda/p/5810513.html
Copyright © 2011-2022 走看看