共计 9873 个字符,预计需要花费 25 分钟才能阅读完成。
一、$()
.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
的部分属性和 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() 的流程图:
(完)