首頁  >  問答  >  主體

javascript - jQuery是如何實作off()對事件的移除的?

我們知道jQuery每個綁定事件的方法都有其對應的移除事件綁定的方法,例如off()對應on(),unbind()對應bind(),die()對應live() ,很好奇這種對匿名事件的解綁是怎麼實現的,jQuery的源碼太深奧又看不太懂,哪位大神能貼上簡化版的程式碼解析下實現原理嗎?

高洛峰高洛峰2708 天前1187

全部回覆(2)我來回復

  • phpcn_u1582

    phpcn_u15822017-06-12 09:29:06

    我覺得要理解off處理,得先理解on的操作,去年讀過jquery2.x的源碼,事件這塊挺複雜的。

    翻看了一下自己粗糙的筆記,關於事件這塊當時講解影片沒有提及自己硬著頭皮看的。

    關於綁定事件可以結合on源碼實作以及jquery.event.add方法:

    我的理解是jquery主要對元素設置緩存數據cache,cache存儲了events變量(事件回調隊列集合),以“事件”:“回調函數數組”的形式存儲,用以實現多次給某個dom添加事件時,回呼都能觸發,而實際真正用原生事件綁定的是對這個回呼函數數組的遍歷執行。

    而對於off,先看看off的源碼部分:

    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.remove方法。

    remove方法

    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( core_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
                // (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
            if ( jQuery.isEmptyObject( events ) ) {
                delete elemData.handle;
                data_priv.remove( elem, "events" );
            }
        },

    主要是對元素取得前面on時存放在cache的events變數對事件鍵值對進行刪除等操作。

    如果只是$(xx).off('click'),那麼就是直接遍歷把events裡click事件對應的回調函數組都給刪了,如果off參數還傳了特定的回調函數,那麼則是對回呼數組遍歷比對,刪除對應的回呼函數…

    對於jquery源碼這塊,前期基礎部分推薦看 妙味課堂 的視頻, 其他可以看 http://www.cnblogs.com/aaronj... 大牛博文,或者購置jquery源碼解析類似書籍。

    源碼涉及的細節太多, 一時半會我也整理不出來= =,我就把大致觀點表述一下……有啥理解錯誤請指正~

    回覆
    0
  • phpcn_u1582

    phpcn_u15822017-06-12 09:29:06

    以下是on的程式碼

    function on( elem, types, selector, data, fn, one ) {
        var origFn, type;
    
        // Types can be a map of types/handlers
        if ( typeof types === "object" ) {
    
            // ( types-Object, selector, data )
            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;
        }
    
        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;
            }
        }
        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 );
        } );
    }

    回覆
    0
  • 取消回覆