一、$()
.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
的部分属性和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() 的流程图:
(完)