共计 8449 个字符,预计需要花费 22 分钟才能阅读完成。
一、起源 jQuery.event.add()
方法最终是用 addEventListener
绑定事件的:
elem.addEventListener(type, eventHandle)
而 eventHandle
方法正是等于jQuery.event.dispatch()
:
if (!( eventHandle = elemData.handle) ) {eventHandle = elemData.handle = function( e) { | |
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? | |
jQuery.event.dispatch.apply(elem, arguments) : undefined; | |
}; | |
} |
二、$
.event.dispatch()
作用:
触发绑定的事件的处理程序
源码:
// 源码 5472 行 | |
//nativeEvent 即原生 MouseEvent | |
// 触发事件的处理程序 | |
dispatch: function(nativeEvent) { | |
// 修正 event 对象 | |
// Make a writable jQuery.Event from the native event object | |
var event = jQuery.event.fix(nativeEvent); | |
console.log(event,'event5479') | |
var i, j, ret, matched, handleObj, handlerQueue, | |
args = new Array(arguments.length), | |
// 获取 click 事件的处理程序集合,结构如下://[// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1}, | |
// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2}, | |
// delegateCount:0, | |
//] | |
// 从数据缓存中获取事件处理集合 | |
handlers = (dataPriv.get( this, "events") || {})[event.type] || [], | |
//click:{// trigger:{}, | |
// _default:{} | |
//} | |
special = jQuery.event.special[event.type] || {}; | |
// Use the fix-ed jQuery.Event rather than the (read-only) native event | |
args[0] = event; | |
for (i = 1; i < arguments.length; i++) {args[ i] = arguments[i]; | |
} | |
//this 即目标元素 | |
//delegateTarget: 委托目标 | |
event.delegateTarget = this; | |
// 这段代码压根不会执行,因为全局搜索没找到 preDispatch | |
// Call the preDispatch hook for the mapped type, and let it bail if desired | |
if (special.preDispatch && special.preDispatch.call( this, event) === false ) {return;} | |
// Determine handlers | |
// 结构如下 | |
//[{ | |
// elem:xx, | |
// handlers:[// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1}, | |
// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2}, | |
// ] | |
//}] | |
// 获取 handler 队列 | |
handlerQueue = jQuery.event.handlers.call(this, event, handlers); | |
// Run delegates first; they may want to stop propagation beneath us | |
i = 0; | |
// 没有执行 stopPropagation()的话 | |
console.log(handlerQueue,'handlerQueue5525') | |
// 先判断有没有冒泡 | |
// 再判断有没有阻止剩下的 handler 执行 | |
while (( matched = handlerQueue[ i++] ) && !event.isPropagationStopped()) {console.log(matched,'matched5542') | |
event.currentTarget = matched.elem; | |
j = 0; | |
//handleObj 即单个事件处理程序 | |
// 没有执行 stopImmediatePropagation()的话 | |
// 依次执行每一个 handler | |
while (( handleObj = matched.handlers[ j++] ) && | |
!event.isImmediatePropagationStopped()) {// Triggered event must either 1) have no namespace, or 2) have namespace(s) | |
// a subset or equal to those in the bound event (both can have no namespace). | |
if (!event.rnamespace || event.rnamespace.test( handleObj.namespace) ) { | |
// 通过循环将为 event 添加 handleObj 和 handleObj.data | |
event.handleObj = handleObj; | |
event.data = handleObj.data; | |
// 关键代码,执行事件处理程序 handler | |
ret = (( jQuery.event.special[ handleObj.origType] || {}).handle || | |
handleObj.handler ).apply(matched.elem, args); | |
if (ret !== undefined) { | |
//event.result 赋值 ret | |
if (( event.result = ret) === false ) { | |
// 阻止默认行为 | |
event.preventDefault(); | |
// 阻止冒泡 | |
event.stopPropagation();} | |
} | |
} | |
} | |
} | |
// Call the postDispatch hook for the mapped type | |
if (special.postDispatch) {special.postDispatch.call( this, event); | |
} | |
console.log(handlers,'event5587') | |
//undefined | |
return event.result; | |
}, |
解析:
(1)jQuery.event.fix()
作用:
将原生事件对象 MouseEvent
修正(fix)成 jQuery
的event
对象
源码:
// 源码 5700 行 | |
fix: function(originalEvent) { | |
// 如果存在属性 id 则原样返回(因为已处理成 jQueryEvent)return originalEvent[jQuery.expando] ? | |
originalEvent : | |
new jQuery.Event(originalEvent); | |
}, |
解析:
可以看到 fix
的本质是新建一个 event
对象,再看 jQuery.Event()
方法
(2)jQuery.Event()
源码:
//click,false | |
// 修正 event 对象 | |
// 源码 5777 行 | |
//src 即 MouseEvent | |
jQuery.Event = function(src, props) { | |
// Allow instantiation without the 'new' keyword | |
if (!( this instanceof jQuery.Event) ) {return new jQuery.Event( src, props); | |
} | |
// Event object | |
//src.type=click | |
if (src && src.type) { | |
//MouseEvent | |
this.originalEvent = src; | |
//click | |
this.type = src.type; | |
// Events bubbling up the document may have been marked as prevented | |
// by a handler lower down the tree; reflect the correct value. | |
this.isDefaultPrevented = src.defaultPrevented || | |
src.defaultPrevented === undefined && | |
// Support: Android <=2.3 only | |
src.returnValue === false ? | |
returnTrue : | |
returnFalse; | |
// Create target properties | |
// Support: Safari <=6 - 7 only | |
// Target should not be a text node (#504, #13143) | |
this.target = (src.target && src.target.nodeType === 3) ? | |
src.target.parentNode : | |
src.target; | |
this.currentTarget = src.currentTarget; | |
this.relatedTarget = src.relatedTarget; | |
// Event type | |
} else { | |
//click | |
this.type = src; | |
} | |
// Put explicitly provided properties onto the event object | |
//false | |
if (props) {jQuery.extend( this, props); | |
} | |
// Create a timestamp if incoming event doesn't have one | |
this.timeStamp = src && src.timeStamp || Date.now(); | |
// Mark it as fixed | |
// 修正的标志 | |
this[jQuery.expando] = true; | |
}; |
解析:
简单来说,就是把原生 event
事件上的常用属性赋值到了 jQuery
的event
上
$("#A").on("click" ,function (event) {// 这个就是 jQuery.Event()构建出的 event | |
console.log(event,"A 被点击了") | |
}) |
jQuery
的 event
结构如下:
//click 的 event 就是 jQuery.Event | |
jQuery.Event{ | |
handleObj{ | |
data:undefined, | |
guid: 2, | |
handler:function(){console.log("A 被点击了")},namespace: "clickA", | |
origType: "click", | |
selector: "#B", | |
type: "click.clickA", | |
}, | |
originalEvent:{// 就是 MouseEvent}, | |
target:div#B, | |
type: "click", | |
delegateTarget: div#A, | |
//fix 的标志 | |
jQuery331087940272164138: true, | |
currentTarget: div#A, | |
isDefaultPrevented:xxx, | |
timeStamp:Date.now(), | |
isDefaultPrevented:function(){return false} | |
} |
注意下 originalEvent
和jQuery.extend(this, props)
前者就是原生 MouseEvent,只是将原生 event 作为 jQuery.event 的 originalEvent 属性了;
后者是扩展属性,如果开发者想额外加入自定义属性的话。
(3)dataPriv.get(this, "events")
注意:
jQuery 的数据缓存里的 events 和上面说的 event 是不同的
数据缓存的 events 是用来结构如下:
{ | |
click:[ | |
{ | |
type: "click", | |
origType: "click", | |
data: undefined, | |
handler: function(){console.log("B 委托 A 绑定 click 事件")}, | |
guid: 1, | |
namespace: "", | |
needsContext: undefined, | |
selector: #B, | |
}, | |
{ | |
type: "click", | |
origType: "click", | |
data: undefined, | |
handler: function(){console.log("A 绑定 click 事件")}, | |
guid: 2, | |
namespace: "", | |
needsContext: undefined, | |
selector: undefined, | |
}, | |
// 事件委托的数量 | |
delegateCount:1, | |
], | |
focus:[ | |
{ | |
type: "focus", | |
origType: "focus", | |
data: undefined, | |
handler: function(){console.log("A 绑定 focus 事件")}, | |
guid: 3, | |
namespace: "", | |
needsContext: undefined, | |
selector: undefined, | |
}, | |
delegateCount:0, | |
], | |
} |
(4) jQuery.event.handlers
作用:
获取 handler
队列
源码:
jQuery.event = { | |
// 源码 5547 行 | |
// 组装事件处理队列 | |
//event 是 fix 过的 MouseEvent, handlers | |
handlers: function(event, handlers) { | |
var i, handleObj, sel, matchedHandlers, matchedSelectors, | |
handlerQueue = [], | |
//0 | |
delegateCount = handlers.delegateCount, | |
// 目标元素 | |
cur = event.target; | |
//handlers,第一个 handler 是委托事件,第二个 handler 是自身事件 | |
// Find delegate handlers | |
if ( delegateCount && | |
// Support: IE <=9 | |
// Black-hole SVG <use> instance trees (trac-13180) | |
cur.nodeType && | |
// Support: Firefox <=42 | |
// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) | |
// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click | |
// Support: IE 11 only | |
// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) | |
!(event.type === "click" && event.button >= 1) ) { | |
// 循环,event.target 冒泡到 cur.parentNode,// 直至绑定的目标元素 #A,退出循环 | |
for (; cur !== this; cur = cur.parentNode || this) {console.log(cur,'cur5618') | |
// Don't check non-elements (#13208) | |
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) | |
if (cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true) ) {matchedHandlers = []; | |
matchedSelectors = {}; | |
// 在每一层,依次将委托的事件 push 进 matchedHandlers | |
// 顺序由下到上 | |
for (i = 0; i < delegateCount; i++) {handleObj = handlers[ i]; | |
//sel 就是 #C | |
// Don't conflict with Object.prototype properties (#13203) | |
sel = handleObj.selector + " "; | |
if (matchedSelectors[ sel] === undefined ) {matchedSelectors[ sel] = handleObj.needsContext ? | |
jQuery(sel, this).index(cur) > -1 : | |
// 注意:jQuery.find()和 jQuery().find()是不一样的 | |
jQuery.find(sel, this, null, [ cur] ).length; | |
} | |
if (matchedSelectors[ sel] ) {matchedHandlers.push( handleObj); | |
} | |
} | |
// 然后将该层委托事件的数组放进 handlers 中 | |
//handlerQueue 是所有层委托事件的集合 | |
if (matchedHandlers.length) {handlerQueue.push( { elem: cur, handlers: matchedHandlers} ); | |
} | |
} | |
} | |
} | |
// Add the remaining (directly-bound) handlers | |
// 最终冒泡到 this 元素 | |
cur = this; | |
//1<2 | |
// 将除委托事件的事件(如自身绑定的事件)放入 handlerQueue 中 | |
if (delegateCount < handlers.length) {handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount) } ); | |
} | |
//[{ | |
// elem:xx, | |
// handlers:[// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1}, | |
// {type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2}, | |
// ] | |
//}] | |
return handlerQueue; | |
}, | |
} |
解析:
注意下这个双层循环,目的是把每一层的委托事件的集合 push
进matchedHandlers
,然后再将 matchedHandlers
放进 handlerQueue
队列
在处理完每层的委托事件后,将剩下的自身绑定事件再 push
进handlerQueue
队列中
也就是说,handlerQueue
的结构如下:
[ | |
// 委托事件 | |
{ | |
elem:xx, | |
handlers:[{type: "click", origType: "click", data: undefined, handler: ƒ, guid: 1}, | |
{type: "click", origType: "click", data: undefined, handler: ƒ, guid: 2}, | |
] | |
}, | |
// 自身绑定事件 | |
{ | |
elem:xxx, | |
handlers:[{type: "click", origType: "click", data: undefined, handler: ƒ, guid: 3}, | |
{type: "click", origType: "click", data: undefined, handler: ƒ, guid: 4}, | |
] | |
}, | |
] |
(5)回过头再往下看 dispatch
源码,是两个 while
循环,举个例子来说明下:
<div id="A" style="background-color: deeppink"> | |
这是 A | |
<div id="B" style="background-color: bisque"> | |
这是 B | |
</div> | |
</div> | |
$("#A").on("click" ,function (event) {console.log(event,"A 被点击了") | |
}) | |
$("#A").on("click" ,"#B",function (event) {console.log(event,"点击了 B,即 B 委托 A 的 click 事件被点击了") | |
}) |
那么会
先循环并 执行 委托事件,
即 handler=function (event) {console.log(event,"点击了 B,即 B 委托 A 的 click 事件被点击了")}
,
再循环并 执行 目标元素自身绑定事件,
即 handler=function (event) {console.log(event,"A 被点击了")}
前提是 冒泡不被阻止
最后,执行 click
事件的事件处理程序的关键代码如下:
handleObj.handler.apply(matched.elem, args)
(完)