jQ的DOM事件机制: 解除绑定
发布在# 菜鸟解读 jQuery #2014年7月16日view:9253
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

本篇来介绍下关于jQ解除事件的流程:
先来看下对外的api:
$().unbind(); $().undelegate(); $().off()
先来看下结构:

jQuery.fn.extend({
    //...
    off: function( types, selector, fn ) {
        //...
        return this.each(function() {
            jQuery.event.remove( this, types, fn, selector );
        });
    },
    unbind: function( types, fn ) {
        return this.off( types, null, fn );
    },
    undelegate: function( selector, types, fn ) {
        return arguments.length == 1? 
            this.off( selector, "**" ) : 
            this.off( types, selector, fn );
    },
    //...
});

和绑定事件类似,这里可以看出最终都会执行到 .off -> jQuery.event.remove

jQuery.fn.extend({
    //...
    //卸载事件
    //types 可以是空格分开的多个事件类型,已分发的jQEvent对象,jsObj
    //selector 用于移除事件代理
    off: function( types, selector, fn ) {
        //types.handleObj 存在则说明是被 jQuery.event.dispatch(event)分发的jQ事件对象
        if ( types && types.preventDefault && types.handleObj ) {
            var handleObj = types.handleObj;
            //取出相应的参数,递归调用.off,移除当前正在执行的函数
            jQuery( types.delegateTarget ).off(
                handleObj.namespace ? 
                    handleObj.origType + "." + handleObj.namespace : 
                    handleObj.origType,
                handleObj.selector,
                handleObj.handler
            );
            return this;
        }
        if ( typeof types === "object" ) {
            // { click: fn, mouseover: fn } 遍历循环调用
            for ( var type in types ) {
                this.off( type, selector, types[ type ] );
            }
            return this;
        }
        if ( selector === false || typeof selector === "function" ) {
            // ( types fn )
            fn = selector;
            selector = undefined;
        }
        //如果传入的fn是false,则指向returnFalse函数
        if ( fn === false ) {
            fn = returnFalse;
        }
        //遍历dom对象,进行逐一调用
        return this.each(function() {
            jQuery.event.remove( this, types, fn, selector );
        });
    },
    //...
}
jQuery.event = {
    //...
    //移除事件
    remove: function( elem, types, handler, selector, mappedTypes ) {
        // false || {}
        var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
            t, tns, type, origType, namespaces, origCount,
            j, events, special, handle, eventType, handleObj;
        //如果 数据缓存对象上没有events列表则直接返回即可
        if ( !elemData || !(events = elemData.events) ) {
            return;
        }
        //切割事件类型成数组,主要是针对hover事件
        types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
        for ( t = 0; t < types.length; t++ ) {
            // 'click.namespace','click','namespace'
            tns = rtypenamespace.exec( types[t] ) || [];
            type = origType = tns[1];
            namespaces = tns[2];
            // 如果没指定事件类型,则遍历删除所有events内的东西
            if ( !type ) {
                for ( type in events ) {
                    // 这里进行一次拼接 是处理 .space 的情况: 删除指定空间下的所有事件
                    jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
                }
                continue;
            }
            //对某些事件类型进行修正
            special = jQuery.event.special[ type ] || {};
            //selector如果存在,则是想删除委托事件,如果有修正的话,这里使用修正的事件类型
            type = ( selector ? special.delegateType : special.bindType ) || type;
            //type类型监听的 事件队列 数组
            eventType = events[ type ] || [];
            origCount = eventType.length;
            namespaces = namespaces ? 
                new RegExp("(^|\\.)" + 
                    namespaces.split(".").sort().join("\\.(?:.*\\.)?") + 
                    "(\\.|$)") :
                null ;
            for ( j = 0; j < eventType.length; j++ ) {
                handleObj = eventType[ j ];
                if( 
                    // mappedTypes 参数如果传入true,则不进行类型检测
                    ( mappedTypes || origType === handleObj.origType ) &&
                    // 如果传入函数 则检测函数的id和函数进行guid的匹配检测
                    ( !handler || handler.guid === handleObj.guid ) &&
                    // 如果指定了命名空间,则进行空间的检测
                    ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
                    // 如果传入了 选择器,检查和函数指定的selector是否相同,
                    // 或者传入 "**" 的话,则移除所有带selector的函数
                    ( !selector || 
                        selector === handleObj.selector || 
                        selector === "**" && handleObj.selector ) 
                ){
                    // 将函数从数组中移除
                    // 移除的时候同时进行变量减一,以便每次正确的调用到数据中的每项
                    eventType.splice( j--, 1 );
                    // 如果是移除了selector,则维持下delegateCount变量
                    if ( handleObj.selector ) {
                        eventType.delegateCount--;
                    }
                    // 修正事件如果有 remove, 则调用下
                    if ( special.remove ) {
                        special.remove.call( elem, handleObj );
                    }
                }
            }
            // 如果函数队列为空 && 以前队列是有函数的 的时候,则移除主监听函数
            if ( eventType.length === 0 && origCount !== eventType.length ) {
                // 在使用remove移除之前,如果有修正对象的teardown方法,则调用之,然后在使用移除
                if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
                    jQuery.removeEvent( elem, type, elemData.handle );
                }
                //删除空了的属性 events: { 'click':[],'mouseover':[] }
                delete events[ type ];
            }
        }
        //如果 events 为空对象的时候,把 events 和 主监听函数 handle 从缓存中移除
        if ( jQuery.isEmptyObject( events ) ) {
            handle = elemData.handle;
            if ( handle ) {
                handle.elem = null;
            }
            jQuery.removeData( elem, [ "events", "handle" ], true );
        }
    },
    //...
}


这两篇代码有点多哦,还有大约3篇关于"DOM事件"的...
本篇就介绍到这里,总结下:
        通过很多形式开放的api方便用户的各种解除操作 ↓
        处理各种情况的参数,整理完毕后调用统一的内部处理函数 ↓
        各种检测数据合法性,然后删除对应的函数↓
        返回this(jQ对象)

感谢您的阅读,欢迎留言:斧正,交流,提问;

评论
发表评论
暂无评论
WRITTEN BY
前端狮子
JS前端开发工程师 :喜欢研究js,nodejs,html5; 希望结交更多朋友~
TA的新浪微博
PUBLISHED IN
# 菜鸟解读 jQuery #

本栏解读的jQ为1.7.2版本。 本人也是刚开始读起源码,在这里分享下成长的心得。 本人能力有限,也是接触JS不久的初学者,定会有不少解析不全不够明朗【甚至BUG】的地方, 希望各位牛牛多多留言斧正 感谢阅读 ps:由于工作不定时繁忙,本人也无法定期更新,但是会尽量抽时间学习,分享给大家

友情链接 大搜车前端团队博客
我的收藏