资讯专栏INFORMATION COLUMN

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

khs1994 / 3042人阅读

摘要:源码源码行被点击了点击了,即委托的事件被点击了优先添加委托,再添加其他即委托在上的事件数量在下标为的位置插入委托事件解析可以看到,是优先添加委托事件,再添加自身事件,触发事件的时候也是按这个顺序。

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

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

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

举例:

这是A
这是B
这是C
这是D
//在父元素上绑定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 上:

这是A
这是E
$("#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 事件

这是A
这是B
这是C
这是D
$("#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 事件

这是A
这是B
这是C
这是D
$("#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事件的同时,阻止冒泡!

这是A
这是B
这是C
这是D
$("#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覆盖。

(完)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104530.html

相关文章

  • 【面试篇】寒冬求职季之你必须要原生JS(中)

    摘要:如果你还没读过上篇上篇和中篇并无依赖关系,您可以读过本文之后再阅读上篇,可戳面试篇寒冬求职季之你必须要懂的原生上小姐姐花了近百个小时才完成这篇文章,篇幅较长,希望大家阅读时多花点耐心,力求真正的掌握相关知识点。 互联网寒冬之际,各大公司都缩减了HC,甚至是采取了裁员措施,在这样的大环境之下,想要获得一份更好的工作,必然需要付出更多的努力。 一年前,也许你搞清楚闭包,this,原型链,就...

    Mike617 评论0 收藏0
  • 【面试篇】寒冬求职季之你必须要原生JS(上)

    摘要:循环可以使用的范围包括数组和结构某些类似数组的对象对象,以及字符串。只能遍历数组,不能中断,返回值是修改后的数组。除了之外,等,也有同样的问题。声明一个只读的常量。这在语法上,称为暂时性死区。暂时性死区也意味着不再是一个百分百安全的操作。 互联网寒冬之际,各大公司都缩减了HC,甚至是采取了裁员措施,在这样的大环境之下,想要获得一份更好的工作,必然需要付出更多的努力。 一年前,也许你搞清楚闭包...

    AlphaWatch 评论0 收藏0
  • 【面试篇】寒冬求职季之你必须要原生JS(上)

    摘要:只能遍历数组,不能中断,返回值是修改后的数组。这在语法上,称为暂时性死区。作用域链无论是还是查询,都会在当前的作用域开始查找,如果没有找到,就会向上级作用域继续查找目标标识符,每次上升一个作用域,一直到全局作用域为止。 互联网寒冬之际,各大公司都缩减了HC,甚至是采取了裁员措施,在这样的大环境之下,想要获得一份更好的工作,必然需要付出更多的努力。 一年前,也许你搞清楚闭包,this,原...

    宠来也 评论0 收藏0
  • JavaScript事件委托原理

    摘要:概念事件委托,通俗来说就是将元素的事件委托给它的父级或者更外级元素处理。级事件规定的事件流包括三个阶段事件捕获目标阶段事件冒泡原理事件委托就是利用事件冒泡机制实现的。最适合采用事件委托技术的事件包括和。 概念 事件委托,通俗来说就是将元素的事件委托给它的父级或者更外级元素处理。 事件流 事件流描述的是从页面中接收事件的顺序。 事件冒泡:事件开始由最具体的元素接收,然后逐级向上传播到较为...

    lscho 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<