1、起源jQuery.event.add()
方法最終是用addEventListener
綁定事件的:javascript
elem.addEventListener( type, eventHandle )
複製代碼
而eventHandle
方法正是等於jQuery.event.dispatch()
:css
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;
};
}
複製代碼
2、$
.event.dispatch()
做用:
觸發綁定的事件的處理程序java
源碼:node
//源碼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;
},
複製代碼
解析:web
(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()
方法bash
(2)jQuery.Event()
源碼:app
//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
上ide
$("#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 )
複製代碼
(完)