• jQuery event(下)


     前文主要介绍了添加事件监听的方法,本文则主要讲删除事件监听,以及事件模拟。

    jQuery.fn.off

    jQuery.fn.off = function( types, selector, fn ) {
        var handleObj, type;
        // 如果types是对象,其实现在应该说是type,并且拥有preventDefalut和handleObj
        if ( types && types.preventDefault && types.handleObj ) {
            // 通过types获取handleObj
            handleObj = types.handleObj;
            // 转成字符串来取消事件
            jQuery( types.delegateTarget ).off(
                handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
                handleObj.selector,
                handleObj.handler
            );
            return this;
        }
        // 如果types还是对象,那么认为其是是一个map,key对应事件名,value对应处理函数
        if ( typeof types === "object" ) {
            // ( types-object [, selector] )
            // 遍历所有type
            for ( type in types ) {
                this.off( type, selector, types[ type ] );
            }
            return this;
        }
        
        // 如果selector为false,或者selector是个函数
        if ( selector === false || typeof selector === "function" ) {
            // ( types [, fn] )
            // 等同于传进来types和fn
            fn = selector;
            selector = undefined;
        }
        if ( fn === false ) {
            // 如果fn是false,则定义为一个return false的函数
            fn = returnFalse;
        }
        // 遍历所有元素
        return this.each(function() {
            // 使用jQuery.event.remove删除所有事件处理
            jQuery.event.remove( this, types, fn, selector );
        });
    };

    这个方法逻辑还是比较清晰的,尝试处理各种传参方式以后,最终都是利用jQuery.event.remove来删除事件处理函数的。

    jQuery.event.remove 

    jQuery.event.remove = function( elem, types, handler, selector, mappedTypes ) {
    
        var j, origCount, tmp,
            events, t, handleObj,
            special, handlers, type, namespaces, origType,
            // 通过内部缓存获取对象相关数据
            elemData = jQuery.hasData( elem ) && jQuery._data( elem );
    
        // 如果没有相关缓存数据,或者缓存中没有相关处理列表,则这个对象没事件可删除
        if ( !elemData || !(events = elemData.events) ) {
            // 退出
            return;
        }
    
        // types可能是通过空格分隔的多个type,转成数组
        types = ( types || "" ).match( core_rnotwhite ) || [""];
        t = types.length;
        // 遍历所有type
        while ( t-- ) {
            // 分解type和namespace
            tmp = rtypenamespace.exec( types[t] ) || [];
            // 得到type
            type = origType = tmp[1];
            // 得到namespace
            namespaces = ( tmp[2] || "" ).split( "." ).sort();
    
            // 如果type是undefined,即原来的type是.xxx.xxx之类的命名空间
            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("\\.(?:.*\\.|)") + "(\\.|$)" );
    
            // 删除掉满足的事件
            origCount = j = handlers.length;
            while ( j-- ) {
                // 得到事件对象
                handleObj = handlers[ j ];
    
                // 参数mappedTypes存在或当前事件和handleObj中的当前事件相同
                if ( ( mappedTypes || origType === handleObj.origType ) &&
                    // 并且参数handler不存在,或handler的ID与handleObj的ID相同
                    ( !handler || handler.guid === handleObj.guid ) &&
                    // 并且没有命名空间,或者是handleObj的命名空间子集
                    ( !tmp || tmp.test( handleObj.namespace ) ) &&
                    // 并且没有selector,或者selector与handleObj的selector相同,
                    // 或者selector为"**"(表示任意)并且handleObj的selector存在
                    ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
                    // 全部满足则删除掉当前事件对象
                    handlers.splice( j, 1 );
    
                    // 如果handleObj有selector
                    if ( handleObj.selector ) {
                        handlers.delegateCount--;
                    }
                    // 如果特殊事件remove存在,则调用special.remove
                    // 应该和special.add对应,目前应当没什么用
                    if ( special.remove ) {
                        special.remove.call( elem, handleObj );
                    }
                }
            }
    
            // 如果缓存中本来存在事件处理对象,且当前没有事件处理对象
            // 证明全部在上面循环中删除掉了,就清除掉
            // 避免潜在的特殊事件处理程序无限递归
            if ( origCount && !handlers.length ) {
                // 则尝试用special.teardown删除事件对handle的绑定
                if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
                    // 不成功则使用removeEventListener删除绑定
                    // 这里虽然还是这么写但实际上就是removeEventListener了
                    jQuery.removeEvent( elem, type, elemData.handle );
                }
    
                // 删除缓存中对应事件处理函数列表
                delete events[ type ];
            }
        }
    
        // 如果缓存events已经空了,该对象没有任何事件绑定了
        if ( jQuery.isEmptyObject( events ) ) {
            // 在缓存中删除handle
            delete elemData.handle;
    
            // 清除掉events
            jQuery._removeData( elem, "events" );
        }
    };
    • 实际上,主要是删除时要判断事件、处理函数、命名空间等是否匹配,匹配才能删除。
    • 还有就是,如果该事件的处理函数列队空了就需要对该事件解绑定。
    • 如果改时间的事件列表都空了,那么就将主处理器,事件列表都删掉。

    然后剩下的解绑定函数都是由jQuery.fn.off扩展来的。

    jQuery.fn.unbind

    jQuery.fn.unbind: function( types, fn ) {
        return this.off( types, null, fn );
    };

    jQuery.fn.undelegate

    jQuery.fn.undelegate = function( selector, types, fn ) {
        // ( namespace ) or ( selector, types [, fn] )
        return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
    };

    jQuery.fn.trigger

    jQuery.fn.trigger = function( type, data ) {
        return this.each(function() {
            jQuery.event.trigger( type, data, this );
        });
    };

    jQuery.fn.trigger方法直接调用jQuery.event.trigger来模拟发消息。

    下面的jQuery.fn.triggerHandler也是通过jQuery.event.trigger来模拟发消息。

    jQuery.fn.triggerHandler = function( type, data ) {
        var elem = this[0];
        if ( elem ) {
            return jQuery.event.trigger( type, data, elem, true );
        }
    };

    jQuery.event.trigger

    jQuery.event.trigger = function( event, data, elem, onlyHandlers ) {
    
        var i, cur, tmp, bubbleType, ontype, handle, special,
            // 需要触发事件的所有元素队列
            eventPath = [ elem || document ],
            // 指定事件类型
            type = event.type || event,
            // 事件是否有命名空间,有则分割成数组
            namespaces = event.namespace ? event.namespace.split(".") : [];
    
        cur = tmp = elem = elem || document;
    
        // 对于text和comment节点不进行事件处理
        if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
            return;
        }
    
        // 仅对focus/blur事件变种成focusin/out进行处理
        // 如果浏览器原生支持focusin/out,则确保当前不触发他们
        if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
            return;
        }
    
        // 如果type有命名空间
        if ( type.indexOf(".") >= 0 ) {
            // 重新组装事件
            namespaces = type.split(".");
            type = namespaces.shift();
            namespaces.sort();
        }
        // 看看是否需要改成ontype形式
        ontype = type.indexOf(":") < 0 && "on" + type;
    
        // 看看这个是不是由jQuery.Event生成的实例,否则用jQuery.Event改造
        event = event[ jQuery.expando ] ?
            event :
            new jQuery.Event( type, typeof event === "object" && event );
    
        // 对event预处理
        event.isTrigger = true;
        event.namespace = namespaces.join(".");
        event.namespace_re = event.namespace ?
            new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
            null;
    
        // 清除数据,以重新使用
        event.result = undefined;
        // 如果事件没有触发元素,则用elem代替
        if ( !event.target ) {
            event.target = elem;
        }
    
        // 如果data为空,则传入处理函数的是event,否则由data和event组成
        data = data == null ?
            [ event ] :
            jQuery.makeArray( data, [ event ] );
    
        // 尝试通过特殊事件进行处理,必要时候退出函数
        special = jQuery.event.special[ type ] || {};
        if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
            return;
        }
    
        // 如果需要冒泡,特殊事件不需要阻止冒泡,且elem不是window对象
        if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
    
            // 冒泡时是否需要转成别的事件(用于事件模拟)
            bubbleType = special.delegateType || type;
            // 如果不是变形来的foucusin/out事件
            if ( !rfocusMorph.test( bubbleType + type ) ) {
                // 则定义当前元素师父节点
                cur = cur.parentNode;
            }
            // 遍历自身及所有父节点
            for ( ; cur; cur = cur.parentNode ) {
                // 推入需要触发事件的所有元素队列
                eventPath.push( cur );
                // 存一下循环中最后一个cur
                tmp = cur;
            }
    
            // 如果循环中最后一个cur是document,那么事件是需要最后触发到window对象上的
            // 将window对象推入元素队列
            if ( tmp === (elem.ownerDocument || document) ) {
                eventPath.push( tmp.defaultView || tmp.parentWindow || window );
            }
        }
    
        // 触发所有该事件对应元素的事件处理器
        i = 0;
        // 遍历所有元素,并确保事件不需要阻止冒泡
        while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
    
            // 先确定事件绑定类型是delegateType还是bindType
            event.type = i > 1 ?
                bubbleType :
                special.bindType || type;
    
            // 确保缓存中该元素对应事件中包含事件处理器,
            // 则取出主处理器(jQuery handle)来控制所有分事件处理器
            handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
            // 如果主处理器(jQuery handle)存在
            if ( handle ) {
                // 触发处理器
                handle.apply( cur, data );
            }
    
            // 取出原生事件处理器elem.ontype
            // 比如click事件就是elem.onclick
            handle = ontype && cur[ ontype ];
            // 如果原生事件处理器存在,看看需不需要阻止事件在浏览器上的默认动作
            if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
                event.preventDefault();
            }
        }
        // 保存事件类型,因为这时候事件可能变了
        event.type = type;
    
        // 如果不需要阻止默认动作,立即执行
        if ( !onlyHandlers && !event.isDefaultPrevented() ) {
    
            // 尝试通过特殊事件触发默认动作
            if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
                !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
    
                // 调用一个原生的DOM方法具有相同名称的名称作为事件的目标。
                // 例如对于事件click,elem.click()是触发该事件
                // 并确保不对window对象阻止默认事件
                if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
    
                    // 防止我们触发FOO()来触发其默认动作时,onFOO事件又触发了
                    tmp = elem[ ontype ];
    
                    // 清除掉该事件监听
                    if ( tmp ) {
                        elem[ ontype ] = null;
                    }
    
                    // 当我们已经将事件向上起泡时,防止相同事件再次触发
                    jQuery.event.triggered = type;
                    // 触发事件
                    elem[ type ]();
                    // 完成清除标记
                    jQuery.event.triggered = undefined;
    
                    // 事件触发完了,可以把监听重新绑定回去
                    if ( tmp ) {
                        elem[ ontype ] = tmp;
                    }
                }
            }
        }
    
        return event.result;
    };

    模拟触发为了让事件模型在各浏览器上表现一致,花了不少的心思。

    反过来说,浏览器事件模型表现不一致,真心折磨人……orz

  • 相关阅读:
    第一节 软件测试概述(2)
    第一节 软件测试概述(1)
    Sql语句,查询效率
    Js cdn 学习
    Java内部类详解
    UML用例图
    UML类图几种关系的总结
    JFinal框架_定时触发程序
    利用EXCEL函数LINEST进行统计学中的回归分析
    Windows 10 64 Bit 编译安装 PyLucene 8.3.0
  • 原文地址:https://www.cnblogs.com/justany/p/2868722.html
Copyright © 2020-2023  润新知