jQuery源碼分析--Event模塊(2)

  1. 接下來就是觸發事件了。事件觸發後的處理函數的分發主要靠兩個函數,一個jQuery.event.dispatch,一個是jQuery.event.handlers。這個dispatch會調用handlers,而handlers會返回一個數組,這個數組是符合本次事件條件的全部處理函數對象。dispatch只管執行。
    那這個handlers是如何運做的呢。綁定在一個元素上面的非代理事件是確定要被觸發的,因此會全數被返回。主要是代理事件的篩選,jQuery會從觸發了事件(target所指的元素)的元素一級一級的往上檢查selector是否符合,符合就把它返回。這種策略和元素的事件代理會有所不一樣,看下面代碼:
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Event fun</title>
    </head>
    <body>
        <div  style="width:200px;height:600px;background-color: red;">
            <div style="width:200px;height:500px;background-color: blue;">
                <div style="width:200px;height:400px;background-color: green;">
                    <div id="a" style="width:200px;height:300px;background-color: black;">
                        <div style="width:200px;height:200px;background-color: yellow;">
                            
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <script src="../../jquery-2.1.1.js"></script>
        <script>
            $(document).on('click',function(){})
            $(document).on('click','#a',function(){})
            $(document).on('click','div',{
                name:'qq',
                age:'dd'
            },function(){
                console.log(1);//5次 
            })
            var doc = $(document)
            document.onclick = function(e){
                console.log(1);//1次
            }
        </script>
        
    </body>
    </html>

    在這個有5層的html嵌套結構中,當在最內部的div上點擊一下。jQuery的事件代理會觸發5次。元素的會觸發1次。
    上源碼:html

    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)
            if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
    
                for ( ; cur !== this; cur = cur.parentNode || this ) {//從觸發了事件的目標元素,向上找,一直到代理的元素
    
                    // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
                    if ( cur.disabled !== true || event.type !== "click" ) {//排除不支持click的元素
                        matches = [];
                        for ( i = 0; i < delegateCount; i++ ) {
                            handleObj = handlers[ i ];
    
                            // Don't conflict with Object.prototype properties (#13203)
                            sel = handleObj.selector + " ";
    
                            if ( matches[ sel ] === undefined ) {//判斷目標元素是否匹配selector的過濾
                                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;
        },

    接下來就是這個dispatch了,其實也很簡單,就幹了3樣事情。
    一、檢查是否是特殊的事件,若是是優先使用特殊的處理函數
    二、調用函數傳入jQuery事件對象。這就是爲何在處理函數內部的事件對象是jQuery的事件對象了。
    三、檢查上函數的返回值是否是爲false,若是是就調用jQuery.Event對象的原型中的方法阻止冒泡和默認行爲。
    上源碼:node

    dispatch: function( event ) {//event爲原生事件對象  函數做用:主監聽函數
    
            // Make a writable jQuery.Event from the native event object
            event = jQuery.event.fix( event );//建立事件對象
    
            var i, j, ret, matched, handleObj,//ret返回值,matched放置匹配過的響應對象
                handlerQueue = [],//待執行隊列。包括後代元素匹配的代理監聽對象數組  和  當前元素上綁定的普通監聽對象數組。
                args = slice.call( arguments ),//把arguments轉換成真正的數組
                handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],//當前事件類型對應的監聽對象數組
                special = jQuery.event.special[ event.type ] || {};//獲取事件的修正對象
    
            // Use the fix-ed jQuery.Event rather than the (read-only) native event
            args[0] = event;//存儲事件對象
            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
            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.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 ) {//返回值爲false是,阻止冒泡和默認行爲
                            if ( (event.result = ret) === false ) {
                                event.preventDefault();
                                event.stopPropagation();
                            }
                        }
                    }
                }
            }
    
            // Call the postDispatch hook for the mapped type
            if ( special.postDispatch ) {//beforeUnload
                special.postDispatch.call( this, event );
            }
    
            return event.result;
        },

     

  2. 下面到了接觸事件綁定。jQuery中刪除一個事件監聽函數實際上就是從事件處理函數對象的數組中刪除掉一個元素而已。公開的API是off,這個函數是修正參數的,而後在底層調用jQuery.event.remove來刪除。能夠一次過多個事件函數,由於off函數會遞歸的調用本身。
    jQuery.event.remove函數主要遍歷處理函數對象數組,而後檢查一下條件
    一、是否有傳type參數,若是有,是否和當前處理函數對象的type是否相等
    二、是否有傳處理函數,若是有,和綁定的時候是否爲同一個;
    三、是否有傳命名空間參數,若是有,是否和處理函數對象的命名空間是否相等
    四、是否有傳selector參數,若是有,是否和處理函數對象的selector是否相等
    若是上面的條件都符合,就把當前的處理函數對象刪除。

    上源碼
    off: function( types, selector, fn ) {
            var handleObj, type;
            if ( types && types.preventDefault && types.handleObj ) {//使用dispatched分發過的jquery處理函數對象,也就是事件正在被觸發
                // ( 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 是對象,用於一次性移除多個事件類型和過個監聽函數
                // ( types-object [, selector] )
                for ( type in types ) {
                    this.off( type, selector, types[ type ] );
                }
                return this;
            }
            if ( selector === false || typeof selector === "function" ) {//修正參數,selector爲false,或者只傳入兩個參數
                // ( types [, fn] )
                fn = selector;
                selector = undefined;
            }
            if ( fn === false ) {//沒傳fn
                fn = returnFalse;
            }
            return this.each(function() {//調用remove刪除事件
                jQuery.event.remove( this, types, fn, selector );  
            });
        },
    
    remove: function( elem, types, handler, selector, mappedTypes ) {
    
            var j, origCount, tmp,
                events, t, handleObj,
                special, handlers, type, namespaces, origType,
                elemData = data_priv.hasData( elem ) && data_priv.get( 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;//若是有傳selector則修正爲代理事件,不然優先考慮修正爲更好的事件
                handlers = events[ type ] || [];
                tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );//用於檢測已綁定事件和types的命名空間是否同樣
    
                // Remove matching events
                origCount = j = handlers.length;
                while ( j-- ) {
                    handleObj = handlers[ j ];
    
                    if ( ( mappedTypes || origType === handleObj.origType ) &&//mappedTypes不爲真時比較傳入類型和監聽對象的原始事件類型
                        ( !handler || handler.guid === handleObj.guid ) &&//沒有指定監聽函數  或  指定監聽函數與監聽對象具備同樣的id
                        ( !tmp || tmp.test( handleObj.namespace ) ) &&//沒有指定命名空間或者監聽對象的命名空間具備指定的命名空間
                        ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {//沒傳入selector  或   有傳入可是與監聽對象的相等   或   爲"**"(全部)是監聽對象有selector
                        handlers.splice( j, 1 );//從監聽對象數組中刪除
    
                        if ( handleObj.selector ) {//若是刪除了的是代理事件  則修正dele gateCount 以便下一次插入代理事件的正確
                            handlers.delegateCount--;
                        }
                        if ( special.remove ) {//有對應的修正方法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 ) {優先調用teardown移除主監聽函數
                        jQuery.removeEvent( elem, type, elemData.handle );
                    }
    
                    delete events[ type ];//從總監聽對象數組中刪除該類型的監聽對象數組
                }
            }
    
            // Remove the expando if it's no longer used
            if ( jQuery.isEmptyObject( events ) ) {//總監聽對象數組爲空,說明該元素上的全部事件都被移除
                delete elemData.handle;//移除主監聽函數儲存數據的對象
                data_priv.remove( elem, "events" );//移除緩存
            }
        },
相關文章
相關標籤/搜索