jQuery源碼解析之trigger()

1、$().trigger()和$().triggerHandler() 的做用和區別node

(1)trigger("focus") 觸發被選元素上的指定事件(focus)以及事件的默認行爲(好比表單提交);
triggerHandler(xxx) 不會引發事件(好比表單提交)的默認行爲數組

(2)trigger(xxx) 觸發全部匹配元素的指定事件;
triggerHandler(xxx) 只觸發第一個匹配元素的指定事件app

(3)trigger(xxx) 會冒泡;
triggerHandler(xxx) 不會冒泡ide

2、$().trigger()this

$("#one").on("click",function () {
   console.log("one被點擊了")
 })
  
 $("#one").trigger('click')

做用:
看 1、(1)spa

源碼:prototype

//觸發type事件,data是自定義事件的額外參數
    //源碼9014行
    trigger: function( type, data ) {
      return this.each( function() {
        jQuery.event.trigger( type, data, this );
      } );
    },

解析:
本質是調用的jQuery.event.trigger()方法code

3、jQuery.event.trigger()regexp

源碼:對象

//源碼8850行
    //type, data, this
    trigger: function( event, data, elem, onlyHandlers ) {
      var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
        //冒泡路徑數組
        eventPath = [ elem || document ],
        //判斷event是否有'type'屬性,有則取event.type,沒有則取event
        type = hasOwn.call( event, "type" ) ? event.type : event,
        //同上
        namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];

      //當前元素
      cur = lastElement = tmp = elem = elem || document;
      //文本內容或者是註釋則不觸發事件
      // Don't do events on text and comment nodes
      if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
        return;
      }
      //由focus/blur轉變到focusin/out,如今不觸發focus/blur事件
      // focus/blur morphs to focusin/out; ensure we're not firing them right now

      //rfocusMorph:focusin focus|focusout blur
      if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
        return;
      }
      //能夠不看
      if ( type.indexOf( "." ) > -1 ) {

        // Namespaced trigger; create a regexp to match event type in handle()
        namespaces = type.split( "." );
        type = namespaces.shift();
        namespaces.sort();
      }
      //onclick,onfocus等等
      ontype = type.indexOf( ":" ) < 0 && "on" + type;
      //event通常是字符串,因此通常是undefined
      //獲取對應type類型的jQuery.event
      // Caller can pass in a jQuery.Event object, Object, or just an event type string
      event = event[ jQuery.expando ] ?
        event :
        //click,false
        new jQuery.Event( type, typeof event === "object" && event );

      // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
      //onlyHandlers通常爲undefined
      //3
      event.isTrigger = onlyHandlers ? 2 : 3;
      //""
      event.namespace = namespaces.join( "." );
      //null
      event.rnamespace = event.namespace ?
        new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
        null;
      //清空event以防它被複用
      // Clean up the event in case it is being reused
      event.result = undefined;
      //target屬性爲目標DOM元素
      //咱們通常取的e.target.value,也正是目標元素的值
      if ( !event.target ) {
        event.target = elem;
      }
      //複製data並預先考慮event,建立handler集合
      // Clone any incoming data and prepend the event, creating the handler arg list

      //簡單點,就是 data=[event]
      data = data == null ?
        [ event ] :
        jQuery.makeArray( data, [ event ] );

      //賦值有須要特殊處理的type
      // Allow special events to draw outside the lines
      special = jQuery.event.special[ type ] || {};

      if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
        return;
      }

      //提早肯定事件冒泡的路徑
      // Determine event propagation path in advance, per W3C events spec (#9951)
      //冒泡至document,再到window;關注全局的ownerDocument
      // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
      if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
        //click
        bubbleType = special.delegateType || type;

        //clickclick
        //若是不是focus/blur的話,獲取它的父元素
        if ( !rfocusMorph.test( bubbleType + type ) ) {
          cur = cur.parentNode;
        }
        //for循環的語法(a; b; c)
        //a在單次循環開始前執行
        //b是單次循環的條件(這裏即cur存在)
        //c是單次循環結束後執行
        for ( ; cur; cur = cur.parentNode ) {
          console.log(cur,'cur8967')
          //將目標元素的祖先元素都push進數組
          eventPath.push( cur );
          tmp = cur;
        }
        //只有當tmp是document時,將window加上
        // Only add window if we got to document (e.g., not plain obj or detached DOM)
        if ( tmp === ( elem.ownerDocument || document ) ) {
          eventPath.push( tmp.defaultView || tmp.parentWindow || window );
        }
      }
      //觸發冒泡機制
      // Fire handlers on the event path
      i = 0;
      //e.stopPropagation()這是阻止冒泡的方法
      //isPropagationStopped() 檢查是否阻止冒泡了,返回boolean
      while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
        lastElement = cur;
        event.type = i > 1 ?
          bubbleType :
          special.bindType || type;
        console.log(i,'lastElement8987')
        // jQuery handler
        //( dataPriv.get( cur, "events" ) || {} )[ event.type ]
        // 先判斷cur元素的events是否有綁定click
        //dataPriv.get( cur, "handle" ) 
        //再獲取cur元素的click事件處理程序
        //獲取目標元素的觸發事件的事件處理程序
        handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
          //獲取觸發事件的處理程序
          dataPriv.get( cur, "handle" );
        /*讓冒泡元素執行handle,這行代碼是觸發冒泡機制的關鍵*/
        /*在執行click事件的處理程序後,天然就會執行e.stopPropagation(),
        * 從而讓event.isPropagationStopped()=true*/
        if ( handle ) {
          handle.apply( cur, data );
        }
        //接下來處理原生的事件及處理程序
        //click爲onclick
        // Native handler
        handle = ontype && cur[ ontype ];
        //若是有綁定原生onclick事件的話
        if ( handle && handle.apply && acceptData( cur ) ) {
          //執行onclick事件的處理程序
          event.result = handle.apply( cur, data );
          if ( event.result === false ) {
            //阻止元素的默認行爲(如提交表單submit)
            event.preventDefault();
          }
        }
      }
      
      event.type = type;
      //若是沒有人阻止默認行爲的話,如今就阻止
      /*好比觸發<a>的click事件,但不會跳轉*/
      // If nobody prevented the default action, do it now
      if ( !onlyHandlers && !event.isDefaultPrevented() ) {
        if ( ( !special._default ||
          special._default.apply( eventPath.pop(), data ) === false ) &&
          acceptData( elem ) ) {
          //在目標上,用重複的命名調用原生DOM事件,會在window層面上影響其餘元素
          // Call a native DOM method on the target with the same name as the event.
          // Don't do default actions on window, that's where global variables be (#6170)
          if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
            //當咱們觸發FOO事件(如click)時,不要重複觸發它的onFOO(onclick)事件
            // Don't re-trigger an onFOO event when we call its FOO() method
            tmp = elem[ ontype ];
            //將jQuery對象的onclick屬性置爲null
            //好比<a>就不會去跳轉了
            if ( tmp ) {
              elem[ ontype ] = null;
            }
            //阻止重複觸發一樣的事件,由於咱們已經把它冒泡了
            // Prevent re-triggering of the same event, since we already bubbled it above
            jQuery.event.triggered = type;
            //若是已經執行阻止冒泡了,則爲window添加阻止冒泡的監聽
            if ( event.isPropagationStopped() ) {
              lastElement.addEventListener( type, stopPropagationCallback );
            }
            console.log(elem[ type ],'type9053')
            //執行type事件
            elem[ type ]();
            if ( event.isPropagationStopped() ) {
              lastElement.removeEventListener( type, stopPropagationCallback );
            }

            jQuery.event.triggered = undefined;

            if ( tmp ) {
              elem[ ontype ] = tmp;
            }

          }
        }
      }
      return event.result;
    },

解析:

(1)trigger()的冒泡機制的實現

if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) )中,經過eventPath存儲目標元素的祖先元素:

//clickclick
        //若是不是focus/blur的話,獲取它的父元素
        if ( !rfocusMorph.test( bubbleType + type ) ) {
          cur = cur.parentNode;
        }
        //for循環的語法(a; b; c)
        //a在單次循環開始前執行
        //b是單次循環的條件(這裏即cur存在)
        //c是單次循環結束後執行
        for ( ; cur; cur = cur.parentNode ) {
          console.log(cur,'cur8967')
          //將目標元素的祖先元素都push進數組
          eventPath.push( cur );
          tmp = cur;
        }
        //只有當tmp是document時,將window加上
        // Only add window if we got to document (e.g., not plain obj or detached DOM)
        if ( tmp === ( elem.ownerDocument || document ) ) {
          eventPath.push( tmp.defaultView || tmp.parentWindow || window );
        }

經過eventPath.push(cur. parentNode)將冒泡元素裝進數組中,並經過while循環觸發冒泡機制

//觸發冒泡機制
      // Fire handlers on the event path
      i = 0;
      //e.stopPropagation()這是阻止冒泡的方法
      //isPropagationStopped() 檢查是否阻止冒泡了,返回boolean
      while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
        lastElement = cur;
        event.type = i > 1 ?
          bubbleType :
          special.bindType || type;
        console.log(i,'lastElement8987')
        // jQuery handler
        //( dataPriv.get( cur, "events" ) || {} )[ event.type ]
        // 先判斷cur元素的events是否有綁定click
        //dataPriv.get( cur, "handle" ) 
        //再獲取cur元素的click事件處理程序
        //獲取目標元素的觸發事件的事件處理程序
        handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
          //獲取觸發事件的處理程序
          dataPriv.get( cur, "handle" );
        /*讓冒泡元素執行handle,這行代碼是觸發冒泡機制的關鍵*/
        /*在執行click事件的處理程序後,天然就會執行e.stopPropagation(),
        * 從而讓event.isPropagationStopped()=true*/
        if ( handle ) {
          handle.apply( cur, data );
        }
        //接下來處理原生的事件及處理程序
        //click爲onclick
        // Native handler
        handle = ontype && cur[ ontype ];
        //若是有綁定原生onclick事件的話
        if ( handle && handle.apply && acceptData( cur ) ) {
          //執行onclick事件的處理程序
          event.result = handle.apply( cur, data );
          if ( event.result === false ) {
            //阻止元素的默認行爲(如提交表單submit)
            event.preventDefault();
          }
        }
      }

關鍵代碼是handle.apply( cur, data ),它用來執行cur元素的事件的處理程序。

(2)經過e.stopPropagation()來阻止冒泡的原理:

<body>
<script src="jQuery.js"></script>
<div id="one">這是one</div>
<script>
    $("#one").click(function(e){
      //將handle.apply( cur, data );註釋後,冒泡不生效
      e.stopPropagation()
      console.log('one被點擊了')
    })

    $("body").click(function(){
      console.log('body被點擊了')
    })
    //執行trigger()後,會打印one被點擊了和body被點擊了
    $("#one").trigger('click')
</script>
</body>

① 上面這段代碼會先執行$("#one").trigger('click')

② trigger()裏會執行到上面(1)的handle.apply( cur, data );,而handle會執行$("#one")click事件的處理程序:

e.stopPropagation()
      console.log('one被點擊了')

e.stopPropagation()走的是這裏:

//event的屬性賦值
  //源碼5749行
  jQuery.Event.prototype = {
    constructor: jQuery.Event,
    //xxx
    isPropagationStopped: returnFalse, //false
    //xxx
    //xxx
    //當執行e.stopPropagation()後走這邊
    //源碼5767行
    stopPropagation: function() {
      var e = this.originalEvent;
      //isPropagationStopped方法返回true
      this.isPropagationStopped = returnTrue;

      if ( e && !this.isSimulated ) {
        e.stopPropagation();
      }
    },
}

最後讓isPropagationStopped()方法返回true

④ 注意:$().trigger()裏的event也就是click裏的event,因此會影響到while循環地判斷,從而達到阻止冒泡循環的 目的

while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { }

⑤ 爲何說click裏的event$().trigger()裏的event

//event通常是字符串,因此通常是undefined
      //獲取對應type類型的jQuery.event
      // Caller can pass in a jQuery.Event object, Object, or just an event type string
      event = event[ jQuery.expando ] ?
        event :
        //click,false
        new jQuery.Event( type, typeof event === "object" && event );

由於 event 是根據type(click)類型生成的,因此trigger裏的event的部分屬性和clickevent屬性相同。

(3)原生js綁定的事件的執行,如onclick

$("#one").click(function(e){
      console.log('one被點擊了')
    })

    document.getElementById("one").onclick=function(){
      console.log('onclick被點擊了')
    }

仍是在while循環中:

//接下來處理原生的事件及處理程序
        //click爲onclick
        // Native handler
        handle = ontype && cur[ ontype ];
        //若是有綁定原生onclick事件的話
        if ( handle && handle.apply && acceptData( cur ) ) {
          //執行onclick事件的處理程序
          event.result = handle.apply( cur, data );
          if ( event.result === false ) {
            //阻止元素的默認行爲(如提交表單submit)
            event.preventDefault();
          }
        }

也就是說:
在冒泡循環機制中,在執行完jQuery綁定的handler後,會接着執行原生JS 綁定的handler

(4)rfocusMorph

//匹配focusinfocus或者focusoutblur
  //源碼8872行
  var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,

(5)jQuery.makeArray()

做用:
用於將一個相似數組的對象轉換爲真正的數組對象

注意:
類數組對象具備許多數組的屬性(例如length屬性,[]數組訪問運算符等),不過它畢竟不是數組,缺乏從數組的原型對象上繼承下來的內置方法(例如:pop()、reverse()等)。

源碼:

//結果僅供內部使用
    // results is for internal usage only
    //源碼442行
    makeArray: function( arr, results ) {
      var ret = results || [];

      if ( arr != null ) {
        //Object()等效於new Object()
        //先將arr轉爲對象類型,由於js中的array是Object
        if ( isArrayLike( Object( arr ) ) ) {
          //將second合併到first後面
          jQuery.merge( ret,
            typeof arr === "string" ?
              [ arr ] : arr
          );
        } else {
          //ret.push(arr)
          push.call( ret, arr );
        }
      }
      //返回array
      return ret;
    },

$.isArrayLike

做用:
判斷是否是類數組

源碼:

//判斷是否是類數組
  //源碼561行
  function isArrayLike( obj ) {

    // Support: real iOS 8.2 only (not reproducible in simulator)
    // `in` check used to prevent JIT error (gh-2145)
    // hasOwn isn't used here due to false negatives
    // regarding Nodelist length in IE
    //後兩個是兼容性考慮的判斷
    var length = !!obj && "length" in obj && obj.length,
      //obj類型
      type = toType( obj );

    if ( isFunction( obj ) || isWindow( obj ) ) {
      return false;
    }

    return type === "array" || length === 0 ||
      typeof length === "number" && length > 0 && ( length - 1 ) in obj;
  }

(6)最後一個if,觸發trigger()時,阻止jQuery元素的默認行爲

if ( !onlyHandlers && !event.isDefaultPrevented() ){
xxx
xxx
}

綜上,trigger一共作了三件事:

(1)觸發冒泡機制
(2)觸發原生綁定事件
(3)阻止元素默認行爲

最後,附上本身整理的觸發 trigger() 的流程圖:


(完)

相關文章
相關標籤/搜索