1、起源jQuery.event.add()
方法最終是用addEventListener
綁定事件的:node
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; }; }
2、$
.event.dispatch()
做用:
觸發綁定的事件的處理程序緩存
源碼:app
//源碼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; },
解析:ide
(1)jQuery.event.fix()
做用:
將原生事件對象MouseEvent
修正(fix)成jQuery
的event
對象post
源碼:ui
//源碼5700行 fix: function( originalEvent ) { //若是存在屬性id則原樣返回(由於已處理成jQueryEvent) return originalEvent[ jQuery.expando ] ? originalEvent : new jQuery.Event( originalEvent ); },
解析:
能夠看到fix
的本質是新建一個event
對象,再看jQuery.Event()
方法this
(2)jQuery.Event()
源碼:spa
//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
上prototype
$("#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 )
(完)