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

栏目: jQuery · 发布时间: 6年前

内容简介:前言:请

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

前言:

回顾下我之前写的一篇文章: 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 的事件委托顺序:

举例:

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

(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 的结构如下:

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

可以看到,

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 的结构如下:

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

可以看到,

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 的结构如下:

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

可以看到,

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 是否有值,push handleObj

按照我们的例子来看,当 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 只 push length!==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和currenttarget的区别

(完)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

ANSI Common Lisp

ANSI Common Lisp

Paul Graham / Prentice Hall / 1995-11-12 / USD 116.40

For use as a core text supplement in any course covering common LISP such as Artificial Intelligence or Concepts of Programming Languages. Teaching students new and more powerful ways of thinking abo......一起来看看 《ANSI Common Lisp》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试