一、起源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 )
复制代码
(完)