美文网首页
jQuery源码解析之trigger()

jQuery源码解析之trigger()

作者: 小进进不将就 | 来源:发表于2019-06-03 17:50 被阅读0次

    一、$().trigger()和$().triggerHandler() 的作用和区别

    (1)trigger("focus") 触发被选元素上的指定事件(focus)以及事件的默认行为(比如表单提交);
    triggerHandler(xxx) 不会引起事件(比如表单提交)的默认行为

    (2)trigger(xxx) 触发所有匹配元素的指定事件;
    triggerHandler(xxx) 只触发第一个匹配元素的指定事件

    (3)trigger(xxx) 会冒泡;
    triggerHandler(xxx) 不会冒泡

    二、$().trigger()

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

    作用:
    看 一、(1)

    源码:

        //触发type事件,data是自定义事件的额外参数
        //源码9014行
        trigger: function( type, data ) {
          return this.each( function() {
            jQuery.event.trigger( type, data, this );
          } );
        },
    

    解析:
    本质是调用的jQuery.event.trigger()方法

    三、jQuery.event.trigger()

    源码:

        //源码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() 的流程图:


    (完)

    相关文章

      网友评论

          本文标题:jQuery源码解析之trigger()

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