摘要:回調隊列中的元素是對象,代表一個事件回調,擁有多個屬性,如等等,其中是回調函數,在觸發時通過傳遞,具體的在后面講。類型是時鍵表示事件名,規則同上,鍵值表示事件觸發時的回調函數。
jQuery源碼學習之event
jQuery的事件機制為異步回調,事件監聽的屬性、參數和回調的等保存在Data實例中,在元素上保存該對象的引用。有方法handle,內部執行dispatch;有屬性events,其值是鍵值對為事件名和回調隊列的對象。回調隊列是一個對象數組,委托事件排在數組前,其余在后,回調在數組中的順序為調用on添加的順序。回調隊列中的元素是對象,代表一個事件回調,擁有多個屬性,如type/origType/data/handler/guid/selector等等,其中handler是回調函數,data在觸發時通過event.data傳遞,具體的在后面講。觸發事件時,根據類型從events中取出隊列并執行。移除事件監聽時,根據類型獲取回調隊列,從隊列中移除對應函數。
設計思路.on/.one內部都調用了函數on,為元素添加事件監聽。而在函數on內部,首先先對參數類型和數目加以區分,最后再遍歷調用on/one的jq對象,調用jQuery.event.add為每個元素添加事件監聽。
onon接收多個參數,根據參數的類型和個數對on/one的調用方式進行區分。
參數一elem是添加事件監聽的元素。調用.on/.one,this作為第一個參數傳入on(即elem)。
參數二types表示添加監聽的事件,類型是string時,表示監聽的事件,可以是多帶帶一個事件,也可以是用空格分隔開的多個事件的字符串,同時還有可選的命名空間。類型是object時鍵表示事件名,規則同上,鍵值表示事件觸發時的回調函數。
參數三selector是選擇器,過濾觸發事件的子元素,常用于事件委托中。
參數四data是觸發時的可選數據,通過event.data傳遞。
參數五fn是觸發事件時執行的回調。
參數六one表示是否只觸發一次。
使用$().on時,可以傳入一個字符串和函數,表示監聽事件及其回調,也可以傳入一個對象,鍵表示監聽事件,值表示對應事件的回調。on內部先對這兩種調用進行區分,如果selector不是字符,串且data非空,說明selector傳參錯誤,置undefined,調用如.on(typeObj,undefined, data);如果data空,說明調用如.on(typeObj, data)。接著便遍歷types對象,取出事件名及其回調,遞歸調用內部函數on。
接著處理types是字符串的情況。如果data == null && fn == null成立,說明on只收到三個參數,為.on(type,fn)的調用。如果data非空但fn空,說明on收到四個參數,先判斷selector的類型,如果是字符串,說明是委托調用,即.on(type,selector,fn);如果是其他類型,說明第四個參數是data,即.on(type,data,fn)。
然后處理fn和one參數,如果one === 1,即.one()調用,定義一個新的函數,內部執行off解綁事件并調用apply執行函數,回調函數為這個新的函數。
在函數的末尾遍歷elem,為jq對象中的每個元素調用jQuery.event.add綁定事件。
function on( elem, types, selector, data, fn, one ) { var origFn, type; // Types can be a map of types/handlers // 用object key為監聽事件類型 value為handler if ( typeof types === "object" ) { // ( types-Object, selector, data ) // selector空,不是委托 if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { on( elem, type, selector, data, types[ type ], one ); } return elem; } // on只有三個參數 elem types和fn if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { // on有四個參數 if ( typeof selector === "string" ) { // elem types selector fn // ( types, selector, fn ) fn = data; data = undefined; } else { // elem types selector fn // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return elem; } 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 elem.each( function() { jQuery.event.add( this, types, fn, data, selector ); } ); }off
接受三個參數,在方法內對調用情況進行區分,最終遍歷調用off的jQuery對象,為每個元素調用off取消事件監聽。
第一個參數types與on接受的第一個參數types相同,可以是字符串,也可以是對象。同時還有可選的參數selector,表示委托的對象,可選參數fn表示事件處理回調。
如果types有preventDefault/handleObj屬性,說明是個Event對象,從這個事件對象中取出元素、類型和事件回調,實例化jQuery對象,遞歸調用off移除事件。
然后判斷types類型,如果是對象則遍歷每一個屬性名,遞歸調用off。
接著判斷selector類型,如果是false或function類型,說明不是委托;false表示顯示指定非委托,function類型表示調用如off(types,fn),更新fn和selector的值,將seletor賦予fn后,再將undefined賦予selector。經過賦值操作后,fn如果是false則將其指向內部函數returnFalse。
最后遍歷調用off的jQuery對象,調用jQuery.event.remove移除監聽。
jQuery.fn.extend({ 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 ); } ); } })jQuery.event
jQuery.event上添加了眾多屬性和方法,用于管理jQuery事件,并不對外開放,只供內部調用。
globalglobal是一個用于記錄用過的事件的對象,鍵是事件名稱,值是true,只有使用過才會記錄,只有jQuery.event.add會更新global。
addadd用于添加事件監聽,在$.on()/one()內調用,是一個接收5個參數的方法,其說明如下:
elem是添加事件監聽的元素
types是監聽的事件類型,可以是多帶帶一個事件,也可以是用空格分隔開的多個事件的字符串,同時還有可選的命名空間。
handler是事件處理回調
data是觸發事件時傳遞的參數,保存在event.data里
selector 子元素選擇器。
先判斷selector的類型,如果是noData或文本/注釋節點則返回,不添加事件監聽。
如果handler.hanler存在,說明是個對象,將handleObjIn指向handler,并取出handler/selector參數。
如果selector存在,則根據selector從document.documentElement查找子元素。
handler無guid屬性時為其添加。elemData.events不存在時初始化為空對象,并將event指向elemData.events。elemData.handle不存在時為其添加,定義為匿名函數,內部執行dispatch。
types可能是由空格分隔開的多個事件,用正則匹配返回一個數組。遍歷該數組,正則匹配取出可能存在的命名空間。確定事件的類型,拓展handleObj。
如果事件隊列不存在時先初始化為空數組。如果special.add存在,說明是特殊事件,調用special.add。委托事件保存在隊列的前面,其他事件在隊列末尾。如果selector存在,說明是委托事件,調用splice在最后一個委托事件后插入,否則直接push即可。
最后在global中記錄已添加的事件回調類型。
jQuery.event = { add: function( elem, types, handler, data, selector ) { // handleObjIn保存類型是object的handler的引用 var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); // 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是一個obj 鍵handler對應真正的handler 鍵selector對應參數selector if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Ensure that invalid selectors throw exceptions at attach time // Evaluate against documentElement in case elem is a non-element node (e.g., document) if ( selector ) { jQuery.find.matchesSelector( documentElement, selector ); } // Make sure that the handler has a unique ID, used to find/remove it later // 為每個handler添加一個guid if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element"s event structure and main handler, if this is the first // elemData.events不存在時初始化為空對象 if ( !( events = elemData.events ) ) { events = elemData.events = {}; } // elemData.handle不存在時 if ( !( eventHandle = elemData.handle ) ) { 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 !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } // Handle multiple events separated by a space // types為用空格隔開的多個事件 將用多個空格隔開的事件保存在一個數組里 types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { // 類似 click.xxx 的情況 xxx是命名空間 tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers // types[t]為xxx. type空 處理types[t+1] 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 // 確定 special event的事件類型 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 // 第一次要初始化events隊列 if ( !( handlers = events[ type ] ) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle ); } } } // 添加到special中 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 // delegateCount記錄委托事件的多少 委托事件在前面 其余在后 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization // 記錄type類型event已被使用過 jQuery.event.global[ type ] = true; } } // ... }remove
用于刪除綁定在元素上的事件,$().off內部就調用了這個方法。這個方法接受5個參數。
elem是要移除事件監聽的元素
types是要移除監聽的事件類型,可以是多帶帶一個事件,也可以是用空格分隔開的多個事件的字符串,同時還有可選的命名空間。
handler是事件處理回調。
selector是子元素選擇器,委托時才傳值。
mappedTypes表示要移除的類型和事件隊列里的類型是否相同,默認undefined,只有移除所有事件時才傳true。
先判斷是否存在事件,不存在時直接返回。
用正則匹配types,獲取要移除的事件類型type和命名空間,保存同一數組里,表示匹配結果。
while循環遍歷數組,如果type空,移除所有類型的監聽。
判斷是否特殊類型事件,獲取事件處理回調隊列。遍歷回調隊列,判斷當前與所傳參數的origType、guid等是否相同,來決定是否從回調隊列中刪除當前元素;如果selector非空,說明是委托事件,委托數目減一;如果special.remove存在,說明非空對象,是特殊事件,移除特殊事件監聽。
jQuery.event = { // ... remove: function( elem, types, handler, selector, mappedTypes ) { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); // 對象上沒有events對象 說明沒綁定過事件 直接返回 if ( !elemData || !( events = elemData.events ) ) { return; } // Once for each type.namespace in types; type may be omitted // types為用空格隔開的多個事件 將用多個空格隔開的事件保存在一個數組里 types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; 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 // type undefined/空 解綁所有事件 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 ] || []; // handlers為要remove的type的handlers,數組 tmp = tmp[ 2 ] && new RegExp( "(^|.)" + namespaces.join( ".(?:.*.|)" ) + "(.|$)" ); // Remove matching events origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; // 判斷一下屬性 相同時修改從handlers數組中刪除handlers[j] // 事件類型、guid、命名空間、委托的選擇器等 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 ]; } } // Remove data and the expando if it"s no longer used if ( jQuery.isEmptyObject( events ) ) { dataPriv.remove( elem, "handle events" ); } } // ... }dispatch
用于觸發事件,在on添加的回調中執行,接受的參數可以有多個,但顯式指定的只有nativeEvent,為瀏覽器觸發的原生事件。
在該方法內,根據nativeEvent復制一個新的事件對象,接著取出事件處理回調隊列handlers和記錄事件特殊類型的對象special(如果非特殊類型是空對象)。
將傳入dispatch的參數數組復制到數組args中,事件對象arguments[0]替換成上面復制的事件對象。
委托對象默認是當前元素,如果存在鉤子函數preDispatch則執行且該函數返回非false,dispatch才能繼續執行。
調用jQuery.event.handler方法獲取事件隊列handlerQueue。遍歷handlerQueue并執行,如果某個事件回調返回false,則事件停止冒泡、取消默認行為。
如果存在postDispatch則執行。最后返回回調執行返回的結果。
jQuery.event = { // ... dispatch: function( nativeEvent ) { // Make a writable jQuery.Event from the native event object // 原生event var event = jQuery.event.fix( nativeEvent ); // 從緩存的events對象里取出觸發事件的handlers var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event // 使用修飾過的event而不是原生event args[ 0 ] = event; for ( i = 1; i < arguments.length; i++ ) { args[ i ] = arguments[ i ]; } // 記錄觸發事件的委托對象 event.delegateTarget = this; // 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 // 獲取handler隊列 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). if ( !event.rnamespace || event.rnamespace.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; } // ... }后記
jQuery.event除了以上介紹的global/add/remove/event外,還有hanlders/addProp/fix/special等方法和屬性,用于獲取事件隊列、為jQuery.Event原型添加屬性、復制event對象和記錄special事件等。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108180.html
摘要:源碼學習之用于合并對象,可選擇是否深復制。盡管官方文檔明確指出第一個參數是的調用情況并不支持,但是這個版本的源碼中,判斷第一個參數的類型雖有限定是類型,但卻未對其值真假加以限定。調用方式源碼和指向同一個函數,在函數內部,對調用情況進行區分。 jQuery源碼學習之extend $.extend用于合并對象,可選擇是否深復制。使用時,第一個參數為合并后的對象。如果要進行深拷貝,則參數1為...
摘要:源碼學習之的通過回調實現異步,其實現核心是。回調函數隊列中的函數返回時停止觸發回調函數隊列只能被觸發一次記錄上一次觸發隊列傳入的值,新添加到隊列中的函數使用記錄值作為參數,并立即執行。實際是,內部則調用了在定義的局部函數。 jQuery源碼學習之Callbacks jQuery的ajax、deferred通過回調實現異步,其實現核心是Callbacks。 使用方法 使用首先要先新建一個...
摘要:是與服務器交換數據并更新部分網頁的藝術,在不重新加載整個頁面的情況下。對象是的核心,所有現代瀏覽器均支持對象和使用。用于在后臺與服務器交換數據。及時有效地幫助學員解決疑難問題,提高學員的學習積極性。 Asynchronous JavaScript and XML(異步的 JavaScript 和 XML)。 AJAX...
摘要:抽象模式使用的裝飾者模式允許我們在運行時或者在隨后一個點上動態地將兩個或兩個以上的對象和它們的屬性一起擴展或合并為一個單一對象。定義三個對象目的是為了裝飾對象將的額外功能附加到上。 抽象decorator模式 使用jQuery的裝飾者模式 jQuery.extend()允許我們在運行時或者在隨后一個點上動態地將兩個或兩個以上的對象(和它們的屬性)一起擴展(或合并)為一個單一對象。 定義...
閱讀 916·2021-10-27 14:14
閱讀 1741·2021-10-11 10:59
閱讀 1315·2019-08-30 13:13
閱讀 3152·2019-08-29 15:17
閱讀 2750·2019-08-29 13:48
閱讀 488·2019-08-26 13:36
閱讀 2082·2019-08-26 13:25
閱讀 857·2019-08-26 12:24