上一章對於jQuery的事件系統,對於jQuery的一些事件繫結介面做了分析。同時,引入了事件委託的概念。那麼,從本章起,將開始深入到jQuery的事件系統內部,對於其原始碼進行解析。
這一篇是可以獨自拿出來看,與前面兩章雖然有些關係,但是如果只是對於jQuery原始碼有興趣的,並且對前端事件有些理解的,從這章開始看也是可以的。
on方法
上一章提及到jQuery繫結的核心,是落在了on方法身上的。on方法相當於對於jQuery的事件介面進行了統一,讓jQuery其他事件繫結有關的方法,直接呼叫on方法來進行實現。因此,我們將拿on方法作為jQuery事件一開始的一個突破口,來對jQuery事件進行層層分析。下面來看jQuery的on方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
on : function( types, selector, data, fn, /*INTERNAL*/ one ) { var origFn, type; // Types can be a map of types/handlers /* * 第一個引數傳入為物件的處理,類似 * {“event1name”:function(){}, * "event2name":function(){} * } * 這樣的寫法。 * 然後呼叫自身on方法,分別對其進行on方法繫結事件 */ if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { this.on( type, selector, data, types[ type ], one ); } return this; } //對於data與fn為null的情況下的處理 if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; //對於僅僅fn為null的情況下的處理 } 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的情況的處理returnFalse即是直接return False的函式 if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return this; } //如果最後一個引數為1,那麼直接呼叫fn,並解綁。 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++ ); } //直接呼叫jQuery.event.add並傳入處理好的引數 return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); } |
可以發現,作為繫結核心的on方法,其實質上只是對傳進來的引數進行調整處理,並將處理過後的引數,傳入jQuery.event.add中來進行處理,因此,接下來我們將對jQuery.event.add的原始碼來進行分析與閱讀。
jQuery.event.add方法
這個方法比較長,並且內部有許多東西,暫時還不好去讀,因此,我將這一部分分為幾個部分來進行閱讀。為了方便理解,我將其分成幾個部分,然後一一來解讀。我儘量做到對這一部分的講解做到簡單易懂,解讀過程中有不對的地方,還請指出。同時,這一部分的程式碼,除非特別說明,否則全部來自於jQuery.event.add。
繫結部分
首先,我們先直接跳到事件註冊那一部分,即addeventListener,看看他做了些什麼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Init the event handler queue if we're the first // 如果是第一次,那麼初始化事件佇列 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false // 如果獲取特殊事件失敗,那麼採用addEventListener來進行事件註冊 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { // 註冊時將eventHandle來進行註冊,並且僅僅執行一次 elem.addEventListener( type, eventHandle, false ); } } } |
這段程式碼,實際就是對於handlers的初始化,同時,對事件採用addEventListenr進行了繫結。
在這一段程式碼中,我們可以看出,jQuery在繫結事件時,與以下幾個部分有關:
- eventHandle
- events
- handlers
- special
下面將對這幾個部分,以及這幾個部分衍生出來的部分一一進行解讀。同時,以下幾部分程式碼,為了方便閱讀,對本身這部分的程式碼進行了一些抽象(這詞用到這裡怎麼怪怪的),方便於理解
eventHandle部分
1 2 3 4 5 6 7 8 9 10 11 12 |
//獲取資料快取 var elemData = data_priv.get( elem ); ... // 如果elemData沒有handle屬性,給其初始化一個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 !== strundefined && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } |
這裡首先對於我們要操作的結點下的資料快取進行了獲取,同時,將資料快取下的handle屬性,賦給了eventHandle。這裡可以發現,elemData.handle這一部分,並沒有直接對於事件的回撥函式進行處理,而是進一步的使用jQuery.event.dispatch來進行的處理。
jQuery.event.dispatch這裡還不會進行閱讀,我們只要知道,jQuery對於回撥的處理,和jQuery.event.dispatch這部分有關就行了。
events以及handlers部分
1 2 3 4 5 6 7 8 9 |
var //獲取資料快取 elemData = data_priv.get( elem ), events; ... // Init the element's event structure and main handler, if this is the first // 如果elemData沒有events屬性,給其初始化一個events屬性 if ( !(events = elemData.events) ) { events = elemData.events = {}; } |
events部分,來自於要操作節點下的資料快取中的events屬性。而根據之前的操作
1 2 3 4 5 6 |
// Init the event handler queue if we're the first // 如果是第一次,那麼初始化事件佇列 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; ... } |
可以明白,events即是對於操作節點下各種事件的型別進行的記錄。
handlers部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var handlers; ... // Init the event handler queue if we're the first // 如果是第一次,那麼初始化事件佇列 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; ... } ... // Add to the element's handler list, delegates in front // 將事件處理的物件加入處理列表 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } |
handlers其實是作為一個列表的作用,在裡面存放了handleObj的有關內容。
具體存入了什麼,我們接下來來看handleObj具體是什麼。
handleObj部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
var handleObjIn,handleObj; ... // Caller can pass in an object of custom data in lieu of the handler // 對於像{handler:function(){},...}這樣的處理(即就是下面的handleObj的格式),後面可用於對handleObj的擴充套件 if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } ... while ( t-- ) { ... // handleObj is passed to all event handlers // handleObj是作為對事件處理的物件 // 擴充套件handleObj的屬性 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 ); ... // Add to the element's handler list, delegates in front // 將事件處理的物件加入處理列表 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } ... } |
handleObj即就是對於事件處理的一個物件,其包含了事件型別,初始型別,事件id,名稱空間,dom元素,回撥函式等屬性。
所以回到之前的handlers,其放入的內容,也就是事件處理物件。
那麼到了這裡,我們對於jQuery.event.add方法已經有了一些頭緒了。那麼我們來整理下到目前為止的流程
到這裡,add部分做了些什麼,我們大概都清楚了。
再回到之前的程式碼上,我們可以看到,其中的一部分是被while迴圈所包裹的,那這一部分是實現的什麼樣的內容呢?
多事件繫結與名稱空間的處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Handle multiple events separated by a space // rnotwhite = (/\S+/g) // 將空格分開的types變為陣列 types = ( types || "" ).match( rnotwhite ) || [ "" ]; // 獲取事件型別的長度 t = types.length; while ( t-- ) { //rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; //對事件名稱空間進行分割與獲取 //例:click.myPlugin.simple tmp = rtypenamespace.exec( types[t] ) || []; //type則為click type = origType = tmp[1]; //namespaces則為['myPlugin','simple'] namespaces = ( tmp[2] || "" ).split( "." ).sort(); ... } |
這裡,對於多事件(類似於(“mouseover click”))進行了處理,並且通過while迴圈分別對其進行事件註冊。同時,while迴圈中對於事件的名稱空間也進行了處理。
special部分
其實之前已經將這個列舉了出來,不過因為其並不影響之前的對於整個的閱讀,因此,將其拿到最後來說明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var special; ... while( t-- ) { ... // If event changes its type, use the special event handlers for the changed type // 如果事件改變了其type,採用特殊事件處理 special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type // 如果selector定義了,就採用special事件的type處理,否則直接採用type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type // 前面type重寫了,因此對special進行一次update special = jQuery.event.special[ type ] || {}; ... // 特殊事件使用add對其進行處理 if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } ... } |
因為瀏覽器相容性的問題,對於一些事件並不能相容。因此,對於這些事件,jQuery採用了special event的機制來對一些特殊的事件來進行處理,以達到跨瀏覽器支援事件的功能。
結語
距離上次寫第二篇到現在也好久了,人還是太懶了,寫的也很慢。這一章對於on與event.add方法進行了解讀,下一章將對於這一章所沒講到的事件委派來進行解析,嗯,還是因為人懶,時間未知。