美文网首页
jQuery源码解析之jQuery.event.dispatch

jQuery源码解析之jQuery.event.dispatch

作者: 小进进不将就 | 来源:发表于2019-06-13 09:46 被阅读0次

    一、起源
    jQuery.event.add()方法最终是用addEventListener绑定事件的:

    elem.addEventListener( type, eventHandle )
    

    eventHandle方法正是等于jQuery.event.dispatch()

      if ( !( eventHandle = elemData.handle ) ) {
            eventHandle = elemData.handle = function( e ) {
              return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
                jQuery.event.dispatch.apply( elem, arguments ) : undefined;
            };
       }
    

    二、$.event.dispatch()
    作用:
    触发绑定的事件的处理程序

    源码:

        //源码5472行
        //nativeEvent即原生MouseEvent
        //触发事件的处理程序
        dispatch: function( nativeEvent ) {
          //修正event对象
          // Make a writable jQuery.Event from the native event object
          var event = jQuery.event.fix( nativeEvent );
          console.log(event,'event5479')
          
          var i, j, ret, matched, handleObj, handlerQueue,
            args = new Array( arguments.length ),
            //获取click事件的处理程序集合,结构如下:
            //[
            // {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
            // {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
            // delegateCount:0,
            //]
            //从数据缓存中获取事件处理集合
            handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
            //click:{
            // trigger:{},
            // _default:{}
            //}
            special = jQuery.event.special[ event.type ] || {};
          // Use the fix-ed jQuery.Event rather than the (read-only) native event
          args[ 0 ] = event;
    
          for ( i = 1; i < arguments.length; i++ ) {
            args[ i ] = arguments[ i ];
          }
          //this即目标元素
          //delegateTarget:委托目标
          event.delegateTarget = this;
          //这段代码压根不会执行,因为全局搜索没找到preDispatch
          // 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
          //结构如下
          //[{
          // elem:xx,
          // handlers:[
          //  {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
          //  {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
          //  ]
          //}]
          //获取handler队列
          handlerQueue = jQuery.event.handlers.call( this, event, handlers );
          // Run delegates first; they may want to stop propagation beneath us
          i = 0;
          //没有执行stopPropagation()的话
          console.log(handlerQueue,'handlerQueue5525')
          //先判断有没有冒泡
          //再判断有没有阻止剩下的handler执行
          while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
            console.log(matched,'matched5542')
            event.currentTarget = matched.elem;
    
            j = 0;
            //handleObj即单个事件处理程序
            //没有执行stopImmediatePropagation()的话
    
            //依次执行每一个handler
            while ( ( handleObj = matched.handlers[ j++ ] ) &&
            !event.isImmediatePropagationStopped() ) {
    
              // Triggered event must either 1) have no namespace, or 2) have namespace(s)
              // a subset or equal to those in the bound event (both can have no namespace).
              if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
                //通过循环将为event添加handleObj和handleObj.data
                event.handleObj = handleObj;
                event.data = handleObj.data;
                //关键代码,执行事件处理程序handler
                ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
                  handleObj.handler ).apply( matched.elem, args );
                if ( ret !== undefined ) {
                  //event.result赋值ret
                  if ( ( event.result = ret ) === false ) {
                    //阻止默认行为
                    event.preventDefault();
                    //阻止冒泡
                    event.stopPropagation();
                  }
                }
              }
            }
          }
    
          // Call the postDispatch hook for the mapped type
          if ( special.postDispatch ) {
            special.postDispatch.call( this, event );
          }
          console.log(handlers,'event5587')
          //undefined
          return event.result;
        },
    

    解析:

    (1)jQuery.event.fix()
    作用:
    将原生事件对象MouseEvent修正(fix)成jQueryevent对象

    源码:

        //源码5700行
        fix: function( originalEvent ) {
          //如果存在属性id则原样返回(因为已处理成jQueryEvent)
          return originalEvent[ jQuery.expando ] ?
            originalEvent :
            new jQuery.Event( originalEvent );
        },
    

    解析:
    可以看到fix的本质是新建一个event对象,再看jQuery.Event()方法

    (2)jQuery.Event()
    源码:

     //click,false
      //修正event对象
      //源码5777行
      //src即MouseEvent
      jQuery.Event = function( src, props ) {
    
        // Allow instantiation without the 'new' keyword
        if ( !( this instanceof jQuery.Event ) ) {
          return new jQuery.Event( src, props );
        }
    
        // Event object
        //src.type=click
        if ( src && src.type ) {
          //MouseEvent
          this.originalEvent = src;
          //click
          this.type = src.type;
    
          // Events bubbling up the document may have been marked as prevented
          // by a handler lower down the tree; reflect the correct value.
          this.isDefaultPrevented = src.defaultPrevented ||
          src.defaultPrevented === undefined &&
    
          // Support: Android <=2.3 only
          src.returnValue === false ?
            returnTrue :
            returnFalse;
    
          // Create target properties
          // Support: Safari <=6 - 7 only
          // Target should not be a text node (#504, #13143)
          this.target = ( src.target && src.target.nodeType === 3 ) ?
            src.target.parentNode :
            src.target;
    
          this.currentTarget = src.currentTarget;
          this.relatedTarget = src.relatedTarget;
    
          // Event type
        } else {
          //click
          this.type = src;
        }
    
        // Put explicitly provided properties onto the event object
        //false
        if ( props ) {
          jQuery.extend( this, props );
        }
    
        // Create a timestamp if incoming event doesn't have one
        this.timeStamp = src && src.timeStamp || Date.now();
    
        // Mark it as fixed
        //修正的标志
        this[ jQuery.expando ] = true;
      };
    

    解析:
    简单来说,就是把原生event事件上的常用属性赋值到了jQueryevent

      $("#A").on("click" ,function (event) {
        //这个就是jQuery.Event()构建出的event
        console.log(event,"A被点击了")
      })
    

    jQueryevent结构如下:

    //click的event就是jQuery.Event
    jQuery.Event{
      handleObj{
        data:undefined,
        guid: 2,
        handler:function(){console.log("A被点击了")},
        namespace: "clickA",
        origType: "click",
        selector: "#B",
        type: "click.clickA",
      },
      originalEvent:{
        //就是MouseEvent
      },
      target:div#B,
      type: "click",
      delegateTarget: div#A,
      //fix 的标志
      jQuery331087940272164138: true,
      currentTarget: div#A,
      isDefaultPrevented:xxx,
      timeStamp:Date.now(),
      isDefaultPrevented:function(){return false}
    }
    

    注意下originalEventjQuery.extend( this, props )
    前者就是原生MouseEvent,只是将原生event作为jQuery.event的originalEvent属性了;
    后者是扩展属性,如果开发者想额外加入自定义属性的话。

    (3)dataPriv.get( this, "events" )
    注意:
    jQuery的数据缓存里的events和上面说的event是不同的

    数据缓存的events是用来结构如下:

    {
      click:[
        {
          type: "click", 
          origType: "click", 
          data: undefined, 
          handler: function(){console.log("B委托A绑定click事件")}, 
          guid: 1,
          namespace: "",
          needsContext: undefined,
          selector: #B,
        },
        {
          type: "click", 
          origType: "click", 
          data: undefined, 
          handler: function(){console.log("A绑定click事件")}, 
          guid: 2,
          namespace: "",
          needsContext: undefined,
          selector: undefined,
        },
        //事件委托的数量
        delegateCount:1,
      ],
      focus:[
        {
          type: "focus", 
          origType: "focus", 
          data: undefined, 
          handler: function(){console.log("A绑定focus事件")}, 
          guid: 3,
          namespace: "",
          needsContext: undefined,
          selector: undefined,
        },
         delegateCount:0,
      ],
    }
    

    (4)jQuery.event.handlers
    作用:
    获取handler队列

    源码:

    jQuery.event = {
        //源码5547行
        //组装事件处理队列  
        //event是fix过的MouseEvent, handlers  
        handlers: function( event, handlers ) {
          var i, handleObj, sel, matchedHandlers, matchedSelectors,
            handlerQueue = [],
            //0
            delegateCount = handlers.delegateCount,
            //目标元素
            cur = event.target;
          //handlers,第一个handler是委托事件,第二个handler是自身事件
          // Find delegate handlers
          if ( delegateCount &&
    
            // Support: IE <=9
            // Black-hole SVG <use> instance trees (trac-13180)
            cur.nodeType &&
    
            // Support: Firefox <=42
            // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
            // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
            // Support: IE 11 only
            // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
            !( event.type === "click" && event.button >= 1 ) ) {
            //循环,event.target冒泡到cur.parentNode,
            //直至绑定的目标元素#A,退出循环
            for ( ; cur !== this; cur = cur.parentNode || this ) {
              console.log(cur,'cur5618')
              // Don't check non-elements (#13208)
              // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
              if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
    
                matchedHandlers = [];
                matchedSelectors = {};
                //在每一层,依次将委托的事件push进matchedHandlers
                //顺序由下到上
                for ( i = 0; i < delegateCount; i++ ) {
                  handleObj = handlers[ i ];
                  //sel就是#C
                  // Don't conflict with Object.prototype properties (#13203)
                  sel = handleObj.selector + " ";
    
                  if ( matchedSelectors[ sel ] === undefined ) {
                    matchedSelectors[ sel ] = handleObj.needsContext ?
                      jQuery( sel, this ).index( cur ) > -1 :
    
                      //注意:jQuery.find()和jQuery().find()是不一样的
                      jQuery.find( sel, this, null, [ cur ] ).length;
                  }
                  if ( matchedSelectors[ sel ] ) {
                    matchedHandlers.push( handleObj );
                  }
                }
                //然后将该层委托事件的数组放进handlers中
                //handlerQueue是所有层委托事件的集合
                if ( matchedHandlers.length ) {
                  handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
                }
              }
            }
          }
    
          // Add the remaining (directly-bound) handlers
          //最终冒泡到this元素
          cur = this;
          //1<2
          //将除委托事件的事件(如自身绑定的事件)放入handlerQueue中
          if ( delegateCount < handlers.length ) {
            handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
          }
          //[{
          // elem:xx,
          // handlers:[
          //  {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
          //  {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
          //  ]
          //}]
          return handlerQueue;
        },
    
    }
    

    解析:
    注意下这个双层循环,目的是把每一层的委托事件的集合pushmatchedHandlers,然后再将matchedHandlers放进handlerQueue队列

    在处理完每层的委托事件后,将剩下的自身绑定事件再pushhandlerQueue队列中

    也就是说,handlerQueue的结构如下:

    [
    //委托事件
      {
       elem:xx,
       handlers:[
          {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1},
          {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2},
        ]
      },
    //自身绑定事件
      {
       elem:xxx,
       handlers:[
          {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 3},
          {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 4},
        ]
      },
    ]
    

    (5)回过头再往下看dispatch源码,是两个while循环,举个例子来说明下:

    <div id="A" style="background-color: deeppink">
      这是A
    
      <div id="B" style="background-color: bisque">
        这是B
      </div>
    
    </div>
    
      $("#A").on("click" ,function (event) {
        console.log(event,"A被点击了")
      })
    
      $("#A").on("click" ,"#B",function (event) {
        console.log(event,"点击了B,即B委托A的click事件被点击了")
      })
    

    那么会
    先循环并执行委托事件,
    handler=function (event) {console.log(event,"点击了B,即B委托A的click事件被点击了")}
    再循环并执行目标元素自身绑定事件,
    handler=function (event) {console.log(event,"A被点击了")}
    前提是冒泡不被阻止

    最后,执行click事件的事件处理程序的关键代码如下:

    handleObj.handler.apply( matched.elem, args )
    

    (完)

    相关文章

      网友评论

          本文标题:jQuery源码解析之jQuery.event.dispatch

          本文链接:https://www.haomeiwen.com/subject/rsprxctx.html