由於jQuery事件管理內容比較多,所以進行了分段,這篇文章主要講的是事件的綁定。
jQuery.fn.on
在選擇元素上綁定一個或多個事件的事件處理函數。
jQuery.fn.on = function( types, selector, data, fn, /*INTERNAL*/ one ) { var origFn, type; // types可以是一個由types/handlers組成的map對象 if ( typeof types === "object" ) { // 如果selector不是字符串 // 則將傳參由( types-Object, selector, data )變成( types-Object, data ) if ( typeof selector !== "string" ) { data = data || selector; selector = undefined; } //遍歷所有type for ( type in types ) { //添加type事件處理函數 this.on( type, selector, data, types[ type ], one ); } return this; } // 如果data為空,且fn為空 if ( data == null && fn == null ) { // 則傳參由( types, selector )變成( types, fn ) fn = selector; data = selector = undefined; // 否則如果只是fn為空 } else if ( fn == null ) { // 如果selector為字符串 if ( typeof selector === "string" ) { // 則傳參從( types, selector, data )變成( types, selector, fn ) fn = data; data = undefined; } else { // 否則傳參從( type, selector, data )變成( types, data, fn ) fn = data; data = selector; selector = undefined; } } //……弄了半天其實就是在模擬重載而已……囧rz if ( fn === false ) { //如果fn為false則變成一個return false的函數 fn = returnFalse; } else if ( !fn ) { //如果fn現在還不存在,則直接return this return this; } // 如果one為1 if ( one === 1 ) { // 保存fn origFn = fn; // 重新定義fn fn = function( event ) { // 這個事件只用一次,用完就用off取消掉。 jQuery().off( event ); return origFn.apply( this, arguments ); } // 使用相同的ID,為了未來好刪除事件 fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } // 對所有用jQuery.event.add來添加 return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); };
文檔中對selector的描述是:
一個選擇器字符串用於過濾器的觸發事件的選擇器元素的后代。如果選擇的< null或省略,當它到達選定的元素,事件總是觸發。
A selector string to filter the descendants of the selected elements that trigger the event. If the selector is
null
or omitted, the event is always triggered when it reaches the selected element.說得貌似很懸乎,不過其實主要是在delegate綁定事件中去過濾元素的一些用不着的后代的。
jQuery.event.add
jQuery.event.add = function( elem, types, handler, data, selector ) { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, // 通過內部緩存獲取元素數據 elemData = jQuery._data( elem ); // 不會沒有數據或者text、comment節點添加事件 if ( !elemData ) { return; } // 如果handler是個包含handler和selector的對象 if ( handler.handler ) { // 則定位必要的參數 handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // 如果handler沒有ID,則給個ID給他 // 用於未來尋找或者刪除handler if ( !handler.guid ) { handler.guid = jQuery.guid++; } // 如果緩存數據中沒有events數據 if ( !(events = elemData.events) ) { // 則初始化events events = elemData.events = {}; } // 如果緩存數據中沒有handle數據 if ( !(eventHandle = elemData.handle) ) { // 定義事件處理函數 eventHandle = elemData.handle = function( e ) { // 取消jQuery.event.trigger第二次觸發事件 // 以及裝卸后的事件 return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; // 定義事件處理器對應的元素,用於防止IE非原生事件中的內存泄露 eventHandle.elem = elem; } // 事件可能是通過空格鍵分隔的字符串,所以將其變成字符串數組 types = ( types || "" ).match( core_rnotwhite ) || [""]; // 事件的長度 t = types.length; // 遍歷所有事件 while ( t-- ) { // 嘗試取出事件的namespace,如aaa.bbb.ccc tmp = rtypenamespace.exec( types[t] ) || []; // 取出事件,如aaa type = origType = tmp[1]; // 取出事件命名空間,如bbb.ccc,並根據"."分隔成數組 namespaces = ( tmp[2] || "" ).split( "." ).sort(); // 事件是否會改變當前狀態,如果會則使用特殊事件 special = jQuery.event.special[ type ] || {}; // 根據是否已定義selector,決定使用哪個特殊事件api,如果沒有非特殊事件,則用type type = ( selector ? special.delegateType : special.bindType ) || type; // 更具狀態改變后的特殊事件 special = jQuery.event.special[ type ] || {}; // 組裝用於特殊事件處理的對象 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 ); // 初始化事件處理列隊,如果是第一次使用 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // 如果獲取特殊事件監聽方法失敗,則使用addEventListener進行添加事件,和attachEvent說88了 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } } } // 通過特殊事件add處理事件 if ( special.add ) { // 添加事件 special.add.call( elem, handleObj ); // 設置處理函數的ID if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // 將事件處理函數推入處理列表 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // 表示事件曾經使用過,用於事件優化 jQuery.event.global[ type ] = true; } // 設置為null避免IE中循環引用導致的內存泄露 elem = null; };
從這里的源代碼看,
- 對於沒有特殊事件特有監聽方法和普通事件都用addEventListener來添加事件了。
- 而又特有監聽方法的特殊事件,則用了另一種方式來添加事件。
special.add從2.0的源代碼來看,似乎沒用用到,看起來是遺留問題,未來也可以根據這個擴展事件模型。
jQuery.event.dispatch
我們先走第一個分支,也就是通過addEventListener觸發jQuery.event.dispatch。
jQuery.event.dispatch = function( event ) { // 重寫原生事件對象,變成一個可讀寫的對象,方便未來修改、擴展 event = jQuery.event.fix( event ); var i, j, ret, matched, handleObj, handlerQueue = [], // 把參數轉成數組 args = core_slice.call( arguments ), // 從內部數據中查找該元素的對應事件處理器列表中的對應處理器,否則為空數組 handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], // 嘗試將事件轉成特殊事件 special = jQuery.event.special[ event.type ] || {}; // 將參數數組第一個元素換成重寫的事件對象 args[0] = event; event.delegateTarget = this; // 嘗試使用特殊事件的preDispatch鈎子來綁定事件,並在必要時退出 if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // 組裝事件處理包{elem, handlerObjs}(這里是各種不同元素)的隊列 handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // Run delegates first; they may want to stop propagation beneath us i = 0; // 遍歷事件處理包{elem, handlerObjs}(取出來則對應一個包了),且事件不需要阻止冒泡 while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { // 定義當前Target為事件處理對象對應的元素 event.currentTarget = matched.elem; j = 0; // 如果事件處理對象{handleObjs}存在(一個元素可能有很多handleObjs),且事件不需要立刻阻止冒泡 while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { // 觸發的事件必須滿足其一: // 1) 沒有命名空間 // 2) 有命名空間,且被綁定的事件是命名空間的一個子集 if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; // 嘗試通過特殊事件獲取處理函數,否則使用handleObj中保存的handler(所以handleObj中還保存有handler) ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); // 如果處理函數存在 if ( ret !== undefined ) { // 如果處理函數返回值是false,則阻止冒泡,阻止默認動作 if ( (event.result = ret) === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // 嘗試通過special.postDispatch勾住這個映射關系,未來可以優化 if ( special.postDispatch ) { special.postDispatch.call( this, event ); } // 返回事件函數 return event.result; };
這里有許多handle相關的東西,具體關系參見以下后面的函數。
jQuery.event.handlers
jQuery.event.handlers = function( event, handlers ) { var i, matches, sel, handleObj, handlerQueue = [], delegateCount = handlers.delegateCount, // 當前事件觸發元素 cur = event.target; // Find delegate handlers // Black-hole SVG <use> instance trees (#13180) // Avoid non-left-click bubbling in Firefox (#3861) // 如果有delegateCount,代表該事件是delegate類型的綁定 // 找出所有delegate的處理函數列隊 if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { // 遍歷元素及元素父級節點 for ( ; cur != this; cur = cur.parentNode || this ) { // 防止單機被禁用的元素時觸發事件 if ( cur.disabled !== true || event.type !== "click" ) { // 開始組裝符合要求的事件處理對象 matches = []; // 便利所有事件處理對象 for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) // 選擇器,用於過濾 sel = handleObj.selector + " "; // 如果matches上沒有綁定該選擇器數量 if ( matches[ sel ] === undefined ) { // 在matches上綁定該選擇器數量 matches[ sel ] = handleObj.needsContext ? // 得出選擇器數量,並賦值 jQuery( sel, this ).index( cur ) >= 0 : jQuery.find( sel, this, null, [ cur ] ).length; } // 再次確定是否綁定選擇器數量 if ( matches[ sel ] ) { // 是則將事件處理對象推入 matches.push( handleObj ); } } // 如果得到的matches里有事件處理對象 if ( matches.length ) { // 組裝成事件處理包(暫時這么叫吧),推入事件處理包隊列 handlerQueue.push({ elem: cur, handlers: matches }); } } } } // 如果還有事件剩余,則將剩余的裝包,推入列隊 if ( delegateCount < handlers.length ) { handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); } return handlerQueue; }
從這里我們可以看出delegate綁定的事件和普通綁定的事件是如何分開的。
對應一個元素,一個event.type的事件處理對象隊列在緩存里只有一個。
區分delegate綁定和普通綁定的方法是:delegate綁定從隊列頭部推入,而普通綁定從尾部推入,通過記錄delegateCount來划分,delegate綁定和普通綁定。
special.setup
// 支持: Firefox 10+ // 創建冒泡的focus和blur事件,即focusin和focusout if ( !jQuery.support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // 通過這個參數來記錄某人focusin/focusout var attaches = 0, // handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); }; // 對需要修復的特殊事件添加方法 jQuery.event.special[ fix ] = { setup: function() { if ( attaches++ === 0 ) { document.addEventListener( orig, handler, true ); } }, teardown: function() { if ( --attaches === 0 ) { document.removeEventListener( orig, handler, true ); } } }; }); }
第二個分支special.setup方法主要是來在Firefox中模擬focusin和focusout事件的,因為各大主流瀏覽器只有他不支持這兩個事件。
由於這兩個方法支持事件冒泡,所以可以用來進行事件代理。
jQuery.event.simulate
然后再利用jQuery.event.simulate來模擬事件觸發。
jQuery.event.simulate = function( type, elem, event, bubble ) { // 重寫事件 var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true, originalEvent: {} } ); // 如果要冒泡 if ( bubble ) { // 利用jQuery.event.trigger模擬觸發事件 jQuery.event.trigger( e, null, elem ); } else { // 否則利用jQuery.event.dispatch來執行處理 jQuery.event.dispatch.call( elem, e ); } // 如果需要阻止默認操作,則阻止 if ( e.isDefaultPrevented() ) { event.preventDefault(); } }
jQuery.fn.one
jQuery.fn.one = function( types, selector, data, fn ) { return this.on( types, selector, data, fn, 1 ); };
運行一次就直接調用this.on,然后在最后參數設置成只用1次就行了。
jQuery.fn.bind
jQuery.fn.bind = function( types, data, fn ) { return this.on( types, null, data, fn ); };
這個也是通過this.on擴展的。
jQuery.fn.live
jQuery.fn.live = function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; };
jQuery.fn.delegate
jQuery.fn.delegate = function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); };
可見jQuery.fn.on是事件添加的核心方法,幾乎所有事件添加方法都是由這一方法擴展出來的。