资讯专栏INFORMATION COLUMN

jQuery源码解析之trigger()

Youngs / 1424人阅读

摘要:一和的作用和区别触发被选元素上的指定事件以及事件的默认行为比如表单提交不会引起事件比如表单提交的默认行为触发所有匹配元素的指定事件只触发第一个匹配元素的指定事件会冒泡不会冒泡二被点击了作用看一源码触发事件,是自定义事件的额外参数源码行解析本

一、$().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;
      //如果没有人阻止默认行为的话,现在就阻止
      /*比如触发的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
            //比如就不会去跳转了
            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()来阻止冒泡的原理:



这是one

① 上面这段代码会先执行$("#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() 的流程图:

(完)

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

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

相关文章

  • jQuery源码解析click()的事件绑定

    摘要:阶段二目标浏览器找到监听器后,就运行该监听器阶段三冒泡目标到祖在事件自下而上到达目标节点的过程中,浏览器会检测不是针对该事件的监听器用来捕获事件,并运行非捕获事件的监听器。注意下这种情况,是在里的具体实现,即调用一次后,就执行,卸载事件。 showImg(https://segmentfault.com/img/remote/1460000019304809); 前言:这篇依旧长,请耐...

    figofuture 评论0 收藏0
  • jQuery事件绑定到触发全过程及知识点补充

    摘要:十的触发机制被点击了元素本身绑定了一个事件,但是是原生事件,它是靠绑定来触发事件的。 showImg(https://segmentfault.com/img/remote/1460000019505402); 前言:最重要的还是最后的流程图,可以试着根据流程图手写实现$().on(),下篇文章会放出模拟实现的代码。 一、举例 这是A 这是C ...

    Jioby 评论0 收藏0
  • jQuery源码解析jQuery.event.dispatch()

    摘要:一起源方法最终是用绑定事件的而方法正是等于二作用触发绑定的事件的处理程序源码源码行即原生触发事件的处理程序修正对象获取事件的处理程序集合,结构如下从数据缓存中获取事件处理集合即目标元素委托目标这段代码压根不会执行,因为全局搜索没找到结构 showImg(https://segmentfault.com/img/remote/1460000019464031); 一、起源jQuery.e...

    GraphQuery 评论0 收藏0

发表评论

0条评论

Youngs

|高级讲师

TA的文章

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