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
的部分属性和click
的event
属性相同。
(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() 的流程图:
(完)