美文网首页
jQuery源码解析之你并不真的懂事件委托及target和cur

jQuery源码解析之你并不真的懂事件委托及target和cur

作者: 小进进不将就 | 来源:发表于2019-06-08 18:05 被阅读0次

    前言:
    回顾下我之前写的一篇文章:JavaScript之事件委托

    一、事件委托(委派)
    含义:
    #A上绑定click事件,但是让#B触发click事件,相当于在 #B 上假绑定了 click 事件

    也就是说:#B 委托了 click 事件给了 #A(在 #A 上绑定)

    举例:

    <div id="A" style="background-color: deeppink">
      这是A
      
      <div id="B" style="background-color: bisque">
        这是B
      
        <div id="C" style="background-color: aqua">
        这是C
        </div>
      
        <div id="D" style="background-color: blueviolet">
        这是D
        </div>
    
      </div>
    </div>
    
      //在父元素上绑定click事件,但只能由子元素触发父元素上绑定的事件
      $("#A").on("click" ,"#B",function (e) {
        console.log("点击了B,即B委托A的click事件被点击了")
      })
      $("#A").on("click" ,"#C",function (e) {
        console.log(e,"点击了C,即C委托A的click事件被点击了")
      })
    

    二、jQuery 的事件委托顺序:

    举例:

    (1)A、B、C 各自绑定了click事件

     $("#A").on("click" ,function () {
       console.log("A被点击了")
     })
    
     $("#B").on("click" ,function () {
       console.log("B被点击了")
     })
    
     $("#C").on("click",function () {
       console.log("C被点击了")
     })
    

    点击 C,会依次执行 C、B、A 的click事件

    输出结果:
    ① C 被点击了
    ② B 被点击了
    ③ A 被点击了

    (2)A 自己绑定了 click 事件,同时 B、C 还委托给 A 绑定 click 事件

     $("#A").on("click" ,function () {
       console.log("A被点击了")
     })
    
     $("#A").on("click" ,"#B",function () {
       console.log("点击了B,即B委托A的click事件被点击了")
     })
    
     $("#A").on("click" ,"#C",function () {
       console.log("点击了C,即C委托A的click事件被点击了")
     })
    

    点击 C,依次执行 C、B 委托给 A 的 click 事件,最后执行 A 自己的 click 事件

    输出结果:
    ① 点击了 C,即 C 委托 A 的 click 事件被点击了
    ② 点击了 B,即 B 委托 A 的 click 事件被点击了
    ③ A 被点击了

    (3)A 自己绑定了 click 事件,同时 B、C 还委托给 A 绑定 click 事件,同时 B、C 还有自己的 click 事件:

     $("#A").on("click" ,function () {
       console.log("A被点击了")
     })
    
     $("#A").on("click" ,"#B",function () {
       console.log("点击了B,即B委托A的click事件被点击了")
     })
    
     $("#A").on("click" ,"#C",function () {
       console.log("点击了C,即C委托A的click事件被点击了")
     })
    
     $("#B").on("click" ,function () {
       console.log("B被点击了")
     })
    
     $("#C").on("click",function () {
       console.log("C被点击了")
     })
    

    点击 C,依次执行:C 自己的事件、B 自己的事件、C 委托给 A 的 click 事件、B委托给 A 的 click 事件、A 自己的 click 事件。

    输出结果:
    ① C 被点击了
    ② B 被点击了
    ③ 点击了 C,即 C 委托 A 的 click 事件被点击了
    ④ 点击了 B,即 B 委托 A 的 click 事件被点击了
    ⑤ A 被点击了

    综上,jQuery事件委托的顺序为:
    (1)先统一处理自身、父元素自身绑定的事件
    (2)再统一处理自身、父元素委托给祖先元素的绑定事件
    (3)最后祖先元素处理自身的事件

    简练说,就是:
    先处理子元素委托给自身的事件,再处理自身的事件。

    源码:
    $().on()—>jQuery.event.add()

    jQuery.event = {
        //源码5241行
        //this, types, fn, data, selector
    
        //#A,'click',function(){console.log('A被点击了')},undefined,undefined
        //#A,'click',function(){点击了C,即C委托A的click事件被点击了},undefined,#C
        add: function( elem, types, handler, data, selector ) {
          xxx
          ...
          //优先添加委托handler,再添加其他handler
          // Add to the element's handler list, delegates in front
          //delegateCount即委托在#A上的事件数量
          if ( selector ) {
            //在下标为handlers.delegateCount++的位置插入委托事件
            handlers.splice( handlers.delegateCount++, 0, handleObj );
          } else {
            handlers.push( handleObj );
          }
    }
    

    解析:
    可以看到,jQuery 是优先添加委托 click 事件,再添加自身 click 事件,触发事件的时候也是按这个顺序。

    注意:
    如下的例子,点击 E 是不能触发 click 事件的,因为冒泡冒不到 A 上:

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

    三、jQuery 绑定事件上的 event 上的 target、currenttarget 和 delegateTarget 的区别?

    target 是触发事件的对象
    delegateTarget 是事件委托的原对象

    而currenttarget分三种情况:
    (1)A 在自身有绑定 click 事件的条件下,C 再去委托 A 绑定 click 事件

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

    点击了C,即 C 委托 A 的 click 事件被点击了
    event 的结构如下:

    可以看到,
    target 是 #C,currenttarget 是 #A,delegateTarget 是 #A

    也就是说:
    target 是触发 click 事件的对象 #C,currenttarget 是 #C 委托绑定click事件的 #A,并且 #A 自身有绑定 click 事件

    ② A被点击了
    target 是 #A,currenttarget 是 #A,delegateTarget 是 #A

    ③ C被点击了
    target 是 #C,currenttarget 是 #C,delegateTarget 是 #C


    (2)A 自身没有绑定 click 事件,C 委托 A 绑定 click 事件

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

    点击了 C,即 C 委托 A 的 click 事件被点击了
    event 的结构如下:

    可以看到,
    target 是 #C,currenttarget 是 #C,而不是 #A,delegateTarget 是 #A

    也就是说:
    target 是触发 click 事件的对象 #C,currenttarget 是 #C,因为 #C 委托 #A 绑定 click 事件,并且 #A 自身没有绑定 click 事件

    ② C被点击了
    target是 #C,currenttarget 是 #C,delegateTarget 是 #C

    (3)A在自身有绑定click事件的条件下,C再去委托A绑定click事件的同时,阻止冒泡!

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

    点击了C,即C委托A的click事件被点击了
    event 的结构如下:

    可以看到,
    target 是 #C,currenttarget 是 #C,而不是 #A,delegateTarget 是 #A

    ② C 被点击了
    target 是 #C,currenttarget 是 #C,delegateTarget 是 #C


    为什么是这样?
    我们来分析下jQuery源码:
    $().on()—>jQuery.event.add()—>elem.addEventListener( type, eventHandle )eventHandle—>jQuery.event.dispatch
    currenttarget在jQuery.event.dispatch中定义,所以我们看jQuery.event.dispatch部分源码:

    jQuery.event = {
      //源码5472行
      //nativeEvent即原生MouseEvent
      dispatch: function( nativeEvent ) {
        //获取handler队列
        handlerQueue = jQuery.event.handlers.call( this, event, handlers );
    
        //如果没有阻止冒泡的话,那么
        while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
            event.currentTarget = matched.elem;
        }
      }
      
        //源码5547行
        //组装事件处理队列  
        //event是fix过的MouseEvent, handlers  
        handlers: function( event, handlers ) {
          //目标元素
          var cur = event.target;
          for ( ; cur !== this; cur = cur.parentNode || this ) {
             if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
                matchedHandlers = [];
                matchedSelectors = {};
                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 );
                  }
                }
              }
    
                if ( matchedHandlers.length ) {
                    handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
               }
          }
    
         // Add the remaining (directly-bound) handlers
         //#A 
         cur = this;
         //1<2 true
        //1<1 false
         //将除委托事件的事件(如自身绑定的事件)放入handlerQueue中
         if ( delegateCount < handlers.length ) {
            handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
          }
      }
    
    }
    

    解析:
    event.currentTarget—>handlerQueue[ i++ ]—>jQuery.event.handlers

    jQuery.event.handlers:
    for循环的意思是:
    (1)只要cur不等于this,即#A,就一直循环
    每次循环:
    (2)将matchedHandlers置为[ ]
    (3)循环委托绑定的事件数量
    循环委托绑定:
    (4)matchedHandlers根据handleObj.selector是否有值,pushhandleObj

    按照我们的例子来看,当 cur=event.target,cur=#C,然后进入冒泡循环,再进入委托事件循环,
    关键是:jQuery.find()
    cur=#C 的时候,matchedSelectors[ sel ]=jQuery.find( sel, this, null, [ cur ] ).length=1
    但是 cur=#B 的时候(冒泡循环),matchedSelectors[ sel ]=0,也就是说jQuery.find()不同于$().find,它是冒泡找 cur 元素!

    所以 matchedHandlers 只 pushlength!==0的委托事件,所以 cur 就是 #C 了(新循环中的当前值)。

    然后

    cur = this;
    

    cur 又等于 this,即 #A,最后将除委托事件的事件(如自身绑定的事件)放入 handlerQueue 中,cur=#A


    再拿例子举,即(2)A 自身没有绑定 click 事件,C 委托 A 绑定 click 事件
    只有一个 handler,并且是委托 handler,

    handlerQueue[
      {
        elem:#C,
        ...
      },
    ]
    //#C
    event.currentTarget = handlerQueue[0].elem
    

    (1)A 在自身有绑定 click 事件的条件下,C 再去委托 A 绑定 click 事件
    有两个 handler

    handlerQueue[
      {
        elem:#C,
        ...
      },
      {
        elem:#A,
        ...
      },
    ]
    //#C
    event.currentTarget = handlerQueue[0].elem
    //#A
    event.currentTarget = handlerQueue[1].elem
    

    因为#A只有一个event,所以在循环handlerQueue[i]时,event.currenttarget最终被#A所覆盖

     while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
            //最终被#A所覆盖
            event.currentTarget = matched.elem;
        }
    

    (3)A在自身有绑定click事件的条件下,C再去委托A绑定click事件的同时,阻止冒泡!
    因为!event.isPropagationStopped(),所以event.currentTarget=#C,未被#A覆盖。


    (完)

    相关文章

      网友评论

          本文标题:jQuery源码解析之你并不真的懂事件委托及target和cur

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