jQuery源碼解析之你並不真的懂事件委託及target和currenttarget的區別

前言:
回顧下我以前寫的一篇文章:JavaScript之事件委託javascript

1、事件委託(委派)
含義:
#A上綁定click事件,可是讓#B觸發click事件,至關於在 #B 上假綁定了 click 事件php

也就是說:#B 委託了 click 事件給了 #A(在 #A 上綁定)java

舉例:node

<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事件被點擊了")
  })
複製代碼

2、jQuery 的事件委託順序:app

舉例:ui

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

 $("#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事件lua

輸出結果:
① C 被點擊了
② B 被點擊了
③ A 被點擊了spa

(2)A 本身綁定了 click 事件,同時 B、C 還委託給 A 綁定 click 事件prototype

 $("#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事件被點擊了")
  })
複製代碼

3、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( thisevent, 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, thisnull, [ 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覆蓋。


(完)

相關文章
相關標籤/搜索