前端框架jquery用的比較多,但一直也沒好好看過源碼,一般就看看使用手冊。這次因為用on注冊委托事件后,沒注意直接用off移除了該元素的指定類型事件,使用時發現被委托的子元素上不會在觸發事件,然后打算一窺究竟jquery的事件具體是怎么實現的,下面記錄一下學習結果,參考的是jquery.1.11.3。
一、jQuery事件注冊 on
測試:$( this.$dom).on("click",".policy-pagin-next",function(){})
$( this.$dom)生成一個jQuery對象,on,off等元素處理函數都在jQuery對象的原型上。
1.1事件注冊 on
jQuery.fn.extend. on=function( types, selector, data, fn, /*INTERNAL*/ one )
首先,需要對用戶在元素上注冊事件時傳入的參數標准化,因為用戶傳入的傳參可以不定的。
如果第一個參數是對象,表示一個元素對象有多個事件需要注冊(如:$(elem).on({click:function(){},mouseenter:function(){}}))。如果標准化后fn===false,則給fn處理函數賦值為function(){return false;}(表示回調函數時取消默認行為和冒泡), 作用是用來取消默認動作的.
One為1時,元素事件觸發時會先刪除事件(jQuery().off( event );),然后回調一次用戶注冊的處理函數(return origFn.apply( this, arguments );)。此處需要為處理函數指定一個id,便於刪除處理函數。fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );(使用相同的guid,便於調用者可以使用origFn刪除)
標准化參數后調用jQuery.event.add( this, types, fn, data, selector );將事件添加到jQ全局變量中。
1.2 事件添加add
jQuery.event. add= function( elem, types, handler, data, selector )
事件添加到jq全局對象中,相關內容存入jQuery.cache緩存中。
1.2.1 獲取一個內部使用對象jQuery._data( elem )
添加事件前需要先需要得到一個內部對象elemData ,這個對象是存事件相關數據的。
elemData = jQuery._data( elem );-> internalData( elem, name, data, true );
先判斷元素是否可以加數據,不要在非元素DOM節點上設置數據。可以加數據的節點如:Element元素節點,Document節點等,不能是applet、embed元素或放flash的object元素。
如果是元素是節點(dom),則需要把事件相關的數據存在jQuery.cache屬性下,否則可以直接存在元素上。
給注冊事件的元素添加標識屬性(類似id的作用,如果是dom元素的事件,其值(由jQuery.guid賦值)對應jQuery.cache中的數據列表中的標識,便於刪除等操作。)
如果jq緩存屬性或元素自身上還沒有存緩存數據,則先存上一個空對象(節點元素)或{ toJSON: jQuery.noop }對象,並返回對象。
1.2.2 給處理函數添加guid標識
handler.guid = jQuery.guid++;
給處理函數加guid,移除事件時的一個判斷條件,( remove中有一條判斷條件是: !handler || handler.guid === handleObj.guid).
1.2.3給內部元素數據(elemData)添加事件相關的屬性內容
elemData添加events屬性用於存儲不同類型事件處理對象。
events = elemData.events;(如果緩存的內部屬性上已有數據則直接獲取)或
events = elemData.events = {};(第一次時初始屬性)
eventHandle = elemData.handle(如果某元素已經有事件注冊過統一處理的回調函數,這里直接返回無需再執行下面這段)
第一次初始存儲元素的內部事件處理函數。某元素注冊的所有事件觸發都將首先回調這個內部封裝的處理函數,jq事件派發后再根據實際的事件類型、選擇器等調用注冊的實際事件類型的處理函數。
eventHandle = elemData.handle = function( e ) { //舍棄jQuery.event.trigger()的第二個事件,當頁面卸載(unloaded )后調用事件 return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; //將elem添加為句柄(處理函數)的屬性,以防止IE非原生事件發生內存泄漏 eventHandle.elem = elem;
handleObj處理對象封裝了一些事件處理的屬性,這個對象會存到指定事件類型的事件處理列表緩存中,這在2.5中使用
handleObj = jQuery.extend({ type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join(".") }, handleObjIn );
1.2.4 初始化事件隊列和綁定全局事件處理函數
// 如果是第一次給這個元素對象添加某類型事件,需要初始化事件處理程序隊列 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener/attachEvent if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false );// 事件句柄在冒泡階段執行 } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } }
1.2.5 給元素的指定事件添加處理函數對象
handlers = events[ type ]
給指定類型的事件添加處理函數,如果是委托事件則將事件處理對象插入事件處理列表的前面,便於先檢查和回調用戶注冊的委托事件處理函數。否則將普通事件直接加在處理對象列表中。
// Add to the element's handler list, delegates in front 添加元素的處理函數列表。 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj );//委托處理對象插入在列表的前面 } else { handlers.push( handleObj ); }
注:調用$(this).data("policypagin",new policyPagin($(this),options)) ; 或_data(實際調用的都是function internalData( elem, name, data, pvt /* Internal Use Only */ ))
Name為policypagin,data為new policyPagin($(this),options),name會自動轉為駝峰格式。緩存在jQuery.cache下面,如下圖所示。
二、元素事件移除off
測試:$( this.$dom).off("click",".policy-pagin-next");
2.1. jQuery.fn. off ( types, selector, fn )
首先參數標准化及相關處理(也可以首參數傳對象表示元素的多個事件移除),然后調用jQuery.event.remove( this, types, fn, selector );進行具體的事件移除操作。
2.2.從元素中移除事件和事件集
jQuery.event.remove= function( elem, types, handler, selector, mappedTypes )
2.2.1獲取元素的緩存數據對象
elemData = jQuery.hasData( elem ) && jQuery._data( elem );
如果是節點,則從jQuery的cache緩存對象中獲取該元素緩存數據內容,否則從對象自身拿緩存數據。
檢查是否有緩存數據:
jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
獲取緩存數據:
cache = isNode ? jQuery.cache : elem,
thisCache = cache[ id ];
2.2.2刪除緩存的處理對象列表中的對應數據(handleObj)
events = elemData.events
handlers = events[ type ] || [];
handlers.splice( j, 1 );
清理jQuery.cache.[id].events屬性下指定類型(如click)事件的數據,先刪除緩存在這個對象上的相關數據,如果處理對象上有選擇器則委托計算器需要做減處理。
刪除事件的條件是:
( ( mappedTypes || origType === handleObj.origType ) &&
( !handler || handler.guid === handleObj.guid ) &&
( !tmp || tmp.test( handleObj.namespace ) ) &&
( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) )
注:不帶空間名的事件類型:元素滿足同類型的事件,如果傳了處理函數則要判斷處理函數的guid是否一致,不傳可以不考慮事件處理函數,如果事件類型帶了空間名稱則需要將空間名放入判斷,如果傳入選擇器則需要進行選擇器匹配。判斷通過則可以刪除指定類型事件處理對象列表的相關處理對象(handleObj),有必要的話需要更新委托計數器。
如果測試$( this.$dom).off("click")則把該dom元素和委托此元素的click事件都刪除了。
帶空間名的事件類型則修復相關參數后再重新執行移除函數。
2.2.3如果某類型事件的處理對象(handleObj)已經刪除完了,需要清理這個類型事件的相關緩存內容
jQuery.removeEvent( elem, type, elemData.handle ); -> elem.removeEventListener( type, handle, false );
如果指定類型的處理對象都刪除了則需要刪除這個元素上注冊的這個事件(使用:removeEventListener)。然后把元素這個類型的事件的緩存數據都清理了delete events[ type ];。
2.2.4當緩存所有事件內容的屬性(events)為空則清理內部首次注冊的函數
如果檢查緩存事件的屬性(dom節點對應的是jQuery.cache.[ elem.expando]. events)為空,則把第一次注冊的處理函數也清理了delete elemData.handle;否則繼續使用不需要清理。
jQuery._removeData( elem, "events" ); -> internalRemoveData( elem, name, true );
如果這個元素的緩存上還有name指定的屬性則刪除緩存上的屬性(這里是events屬性)。
function internalRemoveData( elem, name, pvt )
name需要刪除的屬性名
delete thisCache[ name[i] ];這里把(jQuery.cache.[ elem.expando].events的events刪除)
如果緩存對象指定的name屬性上有數據則后續不需要處理直接返回,否則還需刪除對應緩存id內容等。如緩存對象上沒有數據且這個元素是節點,還需要清理dom節點上增加的屬性(jQuery111300907271903690996)jQuery.cleanData( [ elem ], true );
while ( t-- ) { tmp = rtypenamespace.exec( types[t] ) || []; type = origType = tmp[1]; namespaces = ( tmp[2] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector ? special.delegateType : special.bindType ) || type; handlers = events[ type ] || []; tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); // Remove matching events origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !tmp || tmp.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { handlers.splice( j, 1 ); if ( handleObj.selector ) { handlers.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist 如果我們刪除了一些東西,沒有更多的處理程序存在,需要刪除通用的事件處理程序 // (avoids potential for endless recursion during removal of special event handlers) 避免在刪除特殊事件處理程序期間無限次潛在的遞歸 if ( origCount && !handlers.length ) {//如果指定類型的處理都刪除了則需要刪除 if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } }
三、事件觸發
3.1事件觸發時直接回調的內部處理函數(eventHandle)
內部函數注冊的位置:
elem.addEventListener( type, eventHandle, false );
和
eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; };
Dom元素觸發事件直接回調jquery封裝過的eventHandle函數,
3.2 事件派發
jQuery.event.dispatch= function( event )
3.2.1事件對象修正
event = jQuery.event.fix( event );
兼容一些jquery事件屬性,如target(<IE9,目標對象用的是srcElement);如果事件的目標對象類型是元素或屬性中的文本內容需要修正target為它的parent元素等。
3.2.2獲取元素緩存中的事件處理對象數據
handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
這里得到的是元素指定類型事件的事件處理對象列表,this是eventHandle.elem.
}
然后調用handlerQueue = jQuery.event.handlers.call( this, event, handlers ); 生成處理函數隊列 .
3.2.3 獲取處理函數列表
jQuery.event.handlers=function( event, handlers )
先對handlers處理函數列表中的handleObj 處理對象做選擇器匹配,當事件的目標(觸發)對象event.target和處理對象的selector選出的元素一致時,則將處理對象存入handlerQueue處理函數隊列中。handlerQueue存的是觸發事件的元素和對應的處理對象。
如果元素對象注冊了非委托處理函數,則直接加入隊列handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
3.2.4 先運行委托處理函數后運行元素非委托處理函數
先運行委托代理的事件回調函數,他們可能會阻止冒泡,非委托處理函數可能就不需要運行了。
event.currentTarget由handlerQueue取出的elem來覆蓋。然后調用用戶注冊的事件處理函數handleObj.handler,參數是jQuery.Event,有修改的屬性如data, handleObj等。
event.handleObj = handleObj; event.data = handleObj.data;
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ).apply( matched.elem, args );
如果調用返回的是 return false;
則取消默認行為 和冒泡
if ( (event.result = ret) === false ) { event.preventDefault(); event.stopPropagation(); }
備注:Dom元素的緩存對象
附上源碼:
源碼一:用戶操作事件的接口部分

jQuery.fn.extend({ on: function( types, selector, data, fn, /*INTERNAL*/ one ) { var type, origFn; // Types can be a map of types/handlers如果types是對象,則說明是傳入了多個事件 if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) {//遍歷types對象中的每一個元素,並遞歸調用自身 this.on( type, selector, data, types[ type ], one ); } return this; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } // 如果用戶傳入的事件處理函數是false值,則將事件處理函數賦值為jQuery內部的returnFalse函數 if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return this; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); }, one: function( types, selector, data, fn ) { return this.on( types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { var handleObj, type; if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each(function() { jQuery.event.remove( this, types, fn, selector ); }); }, trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, triggerHandler: function( type, data ) { var elem = this[0]; if ( elem ) { return jQuery.event.trigger( type, data, elem, true ); } } });
源碼二:jquery事件具體實現

jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword // 檢測this是不是Event對象,如果不是,new一個Event對象出來,這樣就避免了外部new對象 if ( !(this instanceof jQuery.Event) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; 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. 文檔document上的事件冒泡可能已經被標記為阻止,由更低層的文檔樹的處理函數阻止了冒泡。 反映正確的值 this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && // Support: IE < 9, Android < 4.0 src.returnValue === false ? returnTrue : returnFalse; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object 將明確提供的屬性放在事件對象上 if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one 如果傳入事件沒有時間戳,則創建一個時間戳 this.timeStamp = src && src.timeStamp || jQuery.now(); // Mark it as fixed 將其標記為已修復(固定)? this[ jQuery.expando ] = true; }; /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. 管理事件的輔助函數 - 不是公共接口的一部分。支持Dean Edwards的addEvent 庫為許多想法。 */ jQuery.event = { global: {}, add: function( elem, types, handler, data, selector ) { var tmp, events, t, handleObjIn, special, eventHandle, handleObj, handlers, type, namespaces, origType, elemData = jQuery._data( elem );// 獲取數據緩存,涉及緩存機制。 不為text、comment節點綁定數據,直接返回 // Don't attach events to noData or text/comment nodes (but allow plain objects) if ( !elemData ) { return; } // Caller can pass in an object of custom data in lieu of the handler // 如果handler是一個有handler屬性或方法的對象,則進行一些轉移賦值操作 if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Make sure that the handler has a unique ID, used to find/remove it later檢查handler是否有一個唯一的id,方便之后查找和刪除.為每一個事件的句柄給一個標示,添加ID的目的是 用來尋找或者刪除handler,因為這個東東是緩存在緩存對象上的,沒有直接跟元素節點發生關聯 if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first如果elemData中沒有events對象,則為其定義events屬性並賦值為空對象 if ( !(events = elemData.events) ) {//元素的事件結構也被直接加到jq對象上 events = elemData.events = {}; } if ( !(eventHandle = elemData.handle) ) {//對同一個dom元素添加事件時,第二次注冊時不用再加上事件處理函數(它是對用戶定義的函數的封裝,和派發)。Handle的jq事件處理被加到了Dom元素對應的jq元素數據上 eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded 舍棄jQuery.event.trigger()的第二個事件,當頁面卸載(unloaded )后調用事件 return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events 將elem添加為句柄(處理函數)的屬性,以防止IE非原生事件發生內存泄漏 eventHandle.elem = elem; } // Handle multiple events separated by a space處理types中傳入的是通過空格分割的多個事件的情況 types = ( types || "" ).match( rnotwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[t] ) || []; type = origType = tmp[1]; namespaces = ( tmp[2] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers if ( !type ) { continue; } // If event changes its type, use the special event handlers for the changed type 如果事件更改其類型,請使用特殊事件處理程序更改類型 special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type 如果選擇器定義,確定特殊事件api類型,否則給定類型 type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers組裝用於特殊事件處理的對象 handleObj = jQuery.extend({ type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join(".") }, handleObjIn ); // Init the event handler queue if we're the first 第一次時初始化事件處理隊列,將同一事件的處理函數放入數組中 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener/attachEvent if the special events handler returns false // 如果獲取特殊事件監聽方法失敗,則使用addEventListener直接對元素對象添加事件 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front 添加到元素的處理程序列表中,代理在前面? if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization跟蹤哪些事件曾被使用過,用於事件優化 jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE elem = null; }, // Detach an event or set of events from an element從元素中移除(分離)事件或事件集 remove: function( elem, types, handler, selector, mappedTypes ) { var j, handleObj, tmp, origCount, t, events, special, handlers, type, namespaces, origType, elemData = jQuery.hasData( elem ) && jQuery._data( elem ); if ( !elemData || !(events = elemData.events) ) { return; } // Once for each type.namespace in types; type may be omitted types = ( types || "" ).match( rnotwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[t] ) || []; type = origType = tmp[1]; namespaces = ( tmp[2] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector ? special.delegateType : special.bindType ) || type; handlers = events[ type ] || []; tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); // Remove matching events origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !tmp || tmp.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { handlers.splice( j, 1 ); if ( handleObj.selector ) { handlers.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist 刪除generic通用的事件處理程序,如果我們刪除了一些東西,沒有更多的處理程序存在 // (avoids potential for endless recursion during removal of special event handlers) 避免在刪除特殊事件處理程序期間無限次潛在的遞歸 if ( origCount && !handlers.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // Remove the expando if it's no longer used 如果不再使用expando,請將其移除 if ( jQuery.isEmptyObject( events ) ) { delete elemData.handle; // removeData also checks for emptiness and clears the expando if empty // so use it instead of delete . removeData還檢查空,如果為空清除expando,則使用它而不是刪除 jQuery._removeData( elem, "events" ); } }, trigger: function( event, data, elem, onlyHandlers ) { var handle, ontype, cur, bubbleType, special, tmp, i, eventPath = [ elem || document ], type = hasOwn.call( event, "type" ) ? event.type : event, namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; cur = tmp = elem = elem || document; // Don't do events on text and comment nodes 不要在文本和注釋節點上執行事件 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } // focus/blur morphs to focusin/out; ensure we're not firing them right now. focus/blur 變形focusin/out; 確保我們現在不觸發 if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf(".") >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle()命名空間觸發器 創建一個正則表達式來匹配handle()中的事件類型 namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); } ontype = type.indexOf(":") < 0 && "on" + type; // Caller can pass in a jQuery.Event object, Object, or just an event type string 調用者可以傳入一個jQuery.Event對象,Object或者只是一個事件類型的字符串 event = event[ jQuery.expando ] ? event : new jQuery.Event( type, typeof event === "object" && event ); // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) event.isTrigger = onlyHandlers ? 2 : 3; event.namespace = namespaces.join("."); event.namespace_re = event.namespace ? new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : null; // Clean up the event in case it is being reused event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list克隆任何傳入的數據並預先處理事件,創建處理程序參數列表 data = data == null ? [ event ] : jQuery.makeArray( data, [ event ] ); // 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) 根據W3C事件規范(#9951)事先確定事件傳播路徑 // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) 冒泡到document,然后到窗口; 監聽全局ownerDocument var(#9724) if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type; if ( !rfocusMorph.test( bubbleType + type ) ) { cur = cur.parentNode; } for ( ; cur; cur = cur.parentNode ) { eventPath.push( cur ); tmp = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM)如果我們得到文檔(例如,不是純對象或分離的DOM),只有添加窗口 if ( tmp === (elem.ownerDocument || document) ) { eventPath.push( tmp.defaultView || tmp.parentWindow || window ); } } // Fire handlers on the event path i = 0; while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { event.type = i > 1 ? bubbleType : special.bindType || type; // jQuery handler handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Native handler handle = ontype && cur[ ontype ]; if ( handle && handle.apply && jQuery.acceptData( cur ) ) { event.result = handle.apply( cur, data ); if ( event.result === false ) { event.preventDefault(); } } } event.type = type; // If nobody prevented the default action, do it now如果沒有人阻止默認操作,請立即執行 if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && jQuery.acceptData( elem ) ) { // Call a native DOM method on the target with the same name name as the event. 在target目標上調用與事件名稱相同的原生DOM方法。 // Can't use an .isFunction() check here because IE6/7 fails that test. // Don't do default actions on window, that's where global variables be (#6170) if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method tmp = elem[ ontype ]; if ( tmp ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above 防止重新觸發相同的事件,因為我們已經冒泡了它 jQuery.event.triggered = type; try { elem[ type ](); } catch ( e ) { // IE<9 dies on focus/blur to hidden element (#1486,#12518) IE <9 focus/blur不能在隱藏元素觸發(#1486,#12518) // only reproducible on winXP IE8 native, not IE9 in IE8 mode只能在winXP IE8本機上重現,而不是IE9在IE8模式下 } jQuery.event.triggered = undefined; if ( tmp ) { elem[ ontype ] = tmp; } } } } return event.result; }, dispatch: function( event ) { // Make a writable jQuery.Event from the native event object 從原生事件對象創建一個可寫的jQuery.Event event = jQuery.event.fix( event ); var i, ret, handleObj, matched, j, handlerQueue = [], args = slice.call( arguments ), handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event 使用已修復的jQuery.Event而不是(只讀)原生事件 args[0] = event; event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired 調用映射類型的preDispatch鈎子,如果需要,讓它返回 if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // Determine handlers 確定處理程序 handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // Run delegates first; they may want to stop propagation beneath us。先運行代理; 他們可能想停止我們向下傳播 i = 0; while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; j = 0; 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). 觸發事件必要:1)沒有命名空間,或2)命名空間是一個子集或等於綁定事件中的命名空間(兩者都不能有命名空間)。 if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { 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 ); } return event.result; }, handlers: function( event, handlers ) { var sel, handleObj, matches, i, handlerQueue = [], delegateCount = handlers.delegateCount, cur = event.target; // Find delegate handlers 查找代理處理程序 // Black-hole SVG <use> instance trees (#13180) 黑洞SVG <use>實例樹(#13180) // Avoid non-left-click bubbling in Firefox (#3861) 在Firefox中避免非左鍵單擊冒泡(#3861) if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { /* jshint eqeqeq: false */ for ( ; cur != this; cur = cur.parentNode || this ) { /* jshint eqeqeq: true */ // Don't check non-elements (#13208) 不檢查非元素(#13208) // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) 不處理禁用元素(#6911,#8165,#11382,#11764) if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { matches = []; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) 防止與Object.prototype屬性沖突(#13203) sel = handleObj.selector + " "; if ( matches[ sel ] === undefined ) { matches[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) >= 0 : jQuery.find( sel, this, null, [ cur ] ).length; } if ( matches[ sel ] ) { matches.push( handleObj ); } } if ( matches.length ) { handlerQueue.push({ elem: cur, handlers: matches }); } } } } // Add the remaining (directly-bound) handlers 添加剩余的(直接綁定)處理程序 if ( delegateCount < handlers.length ) { handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); } return handlerQueue; }, fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; } // Create a writable copy of the event object and normalize some properties 創建事件對象的可寫副本並標准化一些屬性 var i, prop, copy, type = event.type, originalEvent = event, fixHook = this.fixHooks[ type ]; if ( !fixHook ) { this.fixHooks[ type ] = fixHook = rmouseEvent.test( type ) ? this.mouseHooks : rkeyEvent.test( type ) ? this.keyHooks : {}; } copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; event = new jQuery.Event( originalEvent ); i = copy.length; while ( i-- ) { prop = copy[ i ]; event[ prop ] = originalEvent[ prop ]; } // Support: IE<9 // Fix target property (#1925) 修正目標target屬性(#1925) if ( !event.target ) { event.target = originalEvent.srcElement || document; } // Support: Chrome 23+, Safari? // Target should not be a text node (#504, #13143) Target不應該是文本節點(#504,#13143) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } // Support: IE<9 // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) event.metaKey = !!event.metaKey; return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; }, // Includes some event props shared by KeyEvent and MouseEvent props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), fixHooks: {}, keyHooks: { props: "char charCode key keyCode".split(" "), filter: function( event, original ) { // Add which for key events if ( event.which == null ) { event.which = original.charCode != null ? original.charCode : original.keyCode; } return event; } }, mouseHooks: { props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), filter: function( event, original ) { var body, eventDoc, doc, button = original.button, fromElement = original.fromElement; // Calculate pageX/Y if missing and clientX/Y available 如果缺少pageX / Y且clientX / Y可用,然后計算pageX / Y if ( event.pageX == null && original.clientX != null ) { eventDoc = event.target.ownerDocument || document; doc = eventDoc.documentElement; body = eventDoc.body; event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); } // Add relatedTarget, if necessary if ( !event.relatedTarget && fromElement ) { event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; } // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it 按鈕沒有常規化,所以不要使用它 if ( !event.which && button !== undefined ) { event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); } return event; } }, special: { load: { // Prevent triggered image.load events from bubbling to window.load 防止觸發image.load事件冒泡到window.load noBubble: true }, focus: { // Fire native event if possible so blur/focus sequence is correct 如果可能觸發原生事件,,所以blur/focus序列是正確的 trigger: function() { if ( this !== safeActiveElement() && this.focus ) { try { this.focus(); return false; } catch ( e ) { // Support: IE<9 // If we error on focus to hidden element (#1486, #12518), // let .trigger() run the handlers 如果我們錯誤的聚焦隱藏元素(#1486,#12518),讓.trigger()運行處理程序 } } }, delegateType: "focusin" }, blur: { trigger: function() { if ( this === safeActiveElement() && this.blur ) { this.blur(); return false; } }, delegateType: "focusout" }, click: { // For checkbox, fire native event so checked state will be right 對於復選框(checkbox),觸發原生事件,保證checked(選中狀態)的正確 trigger: function() { if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { this.click(); return false; } }, // For cross-browser consistency, don't fire native .click() on links //為了跨瀏覽器的一致性,不觸發鏈接(a標簽)的原生.click()事件 _default: function( event ) { return jQuery.nodeName( event.target, "a" ); } }, beforeunload: { postDispatch: function( event ) { // Support: Firefox 20+ // Firefox doesn't alert if the returnValue field is not set. 如果沒有設置returnValue字段,Firefox不會發出警報。 if ( event.result !== undefined && event.originalEvent ) { event.originalEvent.returnValue = event.result; } } } }, simulate: function( type, elem, event, bubble ) { // Piggyback on a donor event to simulate a different one. // Fake originalEvent to avoid donor's stopPropagation, but if the // simulated event prevents default then we do the same on the donor. 捎帶捐贈者事件來模擬不同的一個。 假原始事件避免捐贈者的停止傳播,但如果模擬事件阻止默認,那么我們對捐贈者也做同樣的事情。 var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true, originalEvent: {} } ); if ( bubble ) { jQuery.event.trigger( e, null, elem ); } else { jQuery.event.dispatch.call( elem, e ); } if ( e.isDefaultPrevented() ) { event.preventDefault(); } } };

jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false );// handle這個也存在jquery的緩存中,即使用戶用的是命名函數,這里也能刪除dom原生的指定事件和事件對應的處理函數。如果用的是委托事件 } } : function( elem, type, handle ) { var name = "on" + type; if ( elem.detachEvent ) { // #8545, #7054, preventing memory leaks for custom events in IE6-8 // detachEvent needed property on element, by name of that event, to properly expose it to GC if ( typeof elem[ name ] === strundefined ) { elem[ name ] = null; } elem.detachEvent( name, handle ); } };
源碼三:jq緩存

jQuery.extend({ cache: {}, // The following elements (space-suffixed to avoid Object.prototype collisions) // throw uncatchable exceptions if you attempt to set expando properties 如果您嘗試設置expando屬性,以下元素(空格后綴以避免Object.prototype沖突)則拋出不可捕獲的異常 noData: { "applet ": true, "embed ": true, // ...but Flash objects (which have this classid) *can* handle expandos 但Flash對象(有這個classid)*可以處理expandos "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" }, hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !isEmptyDataObject( elem ); }, data: function( elem, name, data ) { return internalData( elem, name, data ); }, removeData: function( elem, name ) { return internalRemoveData( elem, name ); }, // For internal use only. 僅限內部使用 _data: function( elem, name, data ) { return internalData( elem, name, data, true ); }, _removeData: function( elem, name ) { return internalRemoveData( elem, name, true ); } });
源碼四:內部數據處理

function internalData( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var ret, thisCache, internalKey = jQuery.expando, // We have to handle DOM nodes and JS objects differently because IE6-7 // can't GC object references properly across the DOM-JS boundary 我們必須將DOM節點和JS對象區別處理,因為IE6-7不能在DOM-JS邊界上正確地進行GC對象引用 isNode = elem.nodeType, // Only DOM nodes need the global jQuery cache; JS object data is // attached directly to the object so GC can occur automatically 只有DOM節點需要全局jQuery緩存; JS對象數據直接附加到對象,因此GC可以自動發生 cache = isNode ? jQuery.cache : elem, // Only defining an ID for JS objects if its cache already exists allows // the code to shortcut on the same path as a DOM node with no cache 只有在JS對象的高速緩存已經存在的情況下,才能定義一個ID,這樣就可以使代碼與沒有高速緩存的DOM節點在同一路徑上進行快捷 id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all 在嘗試獲取沒有數據的對象的數據時,避免再做任何工作 if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { return; } if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache 只有DOM節點需要每個元素的新的唯一ID,因為它們的數據最終在全局緩存中 if ( isNode ) { id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; } else { id = internalKey; } } if ( !cache[ id ] ) { // Avoid exposing jQuery metadata on plain JS objects when the object // is serialized using JSON.stringify 當對象使用JSON.stringify序列化時,避免在純JS對象上暴露jQuery元數據 cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; } // An object can be passed to jQuery.data instead of a key/value pair; this gets // shallow copied over onto the existing cache 一個對象可以傳遞給jQuery.data而不是一個鍵/值對; 這樣會將淺層復制到現有的緩存上 if ( typeof name === "object" || typeof name === "function" ) { if ( pvt ) { cache[ id ] = jQuery.extend( cache[ id ], name ); } else { cache[ id ].data = jQuery.extend( cache[ id ].data, name ); } } thisCache = cache[ id ]; // jQuery data() is stored in a separate object inside the object's internal data // cache in order to avoid key collisions between internal data and user-defined // data. jQuery data()存儲在對象內部數據緩存中的單獨對象中,以避免內部數據和用戶定義數據之間的沖突。 if ( !pvt ) { if ( !thisCache.data ) { thisCache.data = {}; } thisCache = thisCache.data; } if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } // Check for both converted-to-camel and non-converted data property names // If a data property was specified 如果指定了data屬性檢查converted-to-camel(轉換駝峰)和non-converted(未轉換)的數據屬性名稱 if ( typeof name === "string" ) { // First Try to find as-is property data 首先嘗試查找(as-is)原始屬性數據 ret = thisCache[ name ]; // Test for null|undefined property data if ( ret == null ) { // Try to find the camelCased property ret = thisCache[ jQuery.camelCase( name ) ]; } } else { ret = thisCache; } return ret; } 刪除內部數據 function internalRemoveData( elem, name, pvt ) { if ( !jQuery.acceptData( elem ) ) { return; } var thisCache, i, isNode = elem.nodeType, // See jQuery.data for more information cache = isNode ? jQuery.cache : elem, id = isNode ? elem[ jQuery.expando ] : jQuery.expando; // If there is already no cache entry for this object, there is no // purpose in continuing if ( !cache[ id ] ) { return; } if ( name ) { thisCache = pvt ? cache[ id ] : cache[ id ].data; if ( thisCache ) { // Support array or space separated string names for data keys if ( !jQuery.isArray( name ) ) { // try the string as a key before any manipulation if ( name in thisCache ) { name = [ name ]; } else { // split the camel cased version by spaces unless a key with the spaces exists name = jQuery.camelCase( name ); if ( name in thisCache ) { name = [ name ]; } else { name = name.split(" "); } } } else { // If "name" is an array of keys... // When data is initially created, via ("key", "val") signature, // keys will be converted to camelCase. // Since there is no way to tell _how_ a key was added, remove // both plain key and camelCase key. #12786 // This will only penalize the array argument path. name = name.concat( jQuery.map( name, jQuery.camelCase ) ); } i = name.length; while ( i-- ) { delete thisCache[ name[i] ]; } // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { return; } } } // See jQuery.data for more information if ( !pvt ) { delete cache[ id ].data; // Don't destroy the parent cache unless the internal data object // had been the only thing left in it if ( !isEmptyDataObject( cache[ id ] ) ) { return; } } // Destroy the cache if ( isNode ) { jQuery.cleanData( [ elem ], true ); // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) /* jshint eqeqeq: false */ } else if ( support.deleteExpando || cache != cache.window ) { /* jshint eqeqeq: true */ delete cache[ id ]; // When all else fails, null } else { cache[ id ] = null; } }