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

本篇介绍的是jQ的手动触发事件的工作流程:trigger,triggerHandler。
jQ的手动触发也会模拟冒泡过程,先来看下调用时的api;

jQuery.fn.extend({
    //...
    trigger: function( type, data ) {
        return this.each(function() {
            jQuery.event.trigger( type, data, this );
        });
    },
    //手动触发
    triggerHandler: function( type, data ) {
        if ( this[0] ) {
            return jQuery.event.trigger( type, data, this[0], true );
        }
    },
    //...
});

可以看出两者均调用了 jQuery.event.trigger(),
trigger触发所有选中元素的事件并触发浏览器默认行为,
triggerHandler只触发选中的第一个元素的事件并不触发浏览器默认行为也不会模拟冒泡。
下面来看下触发的执行的流程:

jQuery.event = {
    customEvent: {
        "getData": true,
        "setData": true,
        "changeData": true
    },
    //手动触发事件
    trigger: function( event, data, elem, onlyHandlers ) {
        // 文本 和 注释 节点不触发事件
        if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
            return;
        }
        // 事件类型
        var type = event.type || event,
            namespaces = [],
            /*
                cache: 指向全局缓存对象 jQuery.cache
                exclusive: boolean, 是否只触发没有命名空间的事件
                cur, old: 用来后面模拟冒泡时构造路径使用的
                ontype: 用于调用行内监听函数
                handle: 主监听函数或者行内监听函数
                eventPath: 冒泡路径数组
                bubbleType: 当前事件类型对应的冒泡类型
            */
            cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;

        // 如果正在触发focus/blur事件的默认行为,浏览器应该会自动触发focusin/focusout事件
        // jQuery.event.triggered 指示了正在触发默认行为的事件类型,
        // 改属性在触发前被设置了事件类型,触发后被设置为undefined;
        if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
            return;
        }

        //只触发没有命名空间的监听函数
        if ( type.indexOf( "!" ) >= 0 ) {
            type = type.slice(0, -1);
            exclusive = true;
        }

        //指定了命名空间
        if ( type.indexOf( "." ) >= 0 ) {
            namespaces = type.split(".");
            type = namespaces.shift();
            namespaces.sort();
        }

        //这里需要尝试 对于自定义事件的操作情况
        //!jQuery.event.global[ type ] 该事件没有被绑定过,也就不必触发了
        if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
            return;
        }

        // 返回一个封装过的jQ的event事件对象
        event = typeof event === "object" ?
            // jQuery.Event object
            event[ jQuery.expando ] ? event :
            // Object literal
            new jQuery.Event( type, event ) :
            // Just the event type (string)
            new jQuery.Event( type );
        //以下都是对事件对象属性的修正
        event.type = type;
        //正在触发
        event.isTrigger = true;
        //只触发没有命名空间的监听函数
        event.exclusive = exclusive;
        event.namespace = namespaces.join( "." );
        //命名空间的正则
        event.namespace_re = event.namespace ? 
            new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : 
            null ;
        //执行行内事件时,需要用到的事件类型
        ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";

        // 没有传入dom元素的话,则触发所有的带有type事件的事件
        if ( !elem ) {
            cache = jQuery.cache;
            for ( i in cache ) {
                if ( cache[ i ].events && cache[ i ].events[ type ] ) {
                    jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
                }
            }
            return;
        }

        // 清空上一次的事件结果
        event.result = undefined;
        //修正事件属性
        if ( !event.target ) {
            event.target = elem;
        }

        // 将传入进来的数据制作成数组,并将事件对象插入到数组的第一项
        // 这样做是为了后面 apply 方便
        data = data != null ? jQuery.makeArray( data ) : [];
        data.unshift( event );

        // 调用修正对象的 trigger() 方法
        special = jQuery.event.special[ type ] || {};
        if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
            return;
        }

        //这是一个冒泡的触发队列
        eventPath = [[ elem, special.bindType || type ]];
        // onlyHandlers为true,表示只触发当前元素上的事件监听函数,不触发默认行为和冒泡过程
        // 修正对象的noBubble为true,表示不冒泡,比如 load
        // window对象不需要冒泡
        if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
            // 优先使用修正的冒泡类型的事件类型 focus=>focusin
            bubbleType = special.delegateType || type;
            //存入父元素,如果是focus/blue事件的话,则保存当前
            cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
            old = null;
            //循环设置父元素
            for ( ; cur; cur = cur.parentNode ) {
                eventPath.push([ cur, bubbleType ]);
                old = cur;
            }
            // 如果old===document,则把window也添加到队列中
            if ( old && old === elem.ownerDocument ) {
                eventPath.push( [ old.defaultView || old.parentWindow || window, bubbleType ] );
            }
        }

        // 触发路径队列中的每个元素
        for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {

            cur = eventPath[i][0];
            event.type = eventPath[i][1];

            //取出 元素对应的数据缓存中的主监听函数
            handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && 
                jQuery._data( cur, "handle" );

            //触发 主监听函数
            //注意,这里所谓的监听函数指的是jQ封装后的那个,并非用户绑定的
            if( handle ){
                //函数内部触发了 dispatch 事件来实现事件的分发和执行
                handle.apply( cur, data );
            }

            //取出 行内式 的事件,进行触发
            handle = ontype && cur[ ontype ];
            //执行路径元素上绑定的行内事件的监听函数,如果返回了false,
            //则阻止默认行为
            if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
                event.preventDefault();
            }
        }
        event.type = type;

        // 触发浏览器默认行为
        // onlyHandlers:false && 没有阻止默认行为
        if ( !onlyHandlers && !event.isDefaultPrevented() ) {
            if( 
                // 执行修正对象的默认行为
                (   
                    !special._default || 
                    special._default.apply( elem.ownerDocument, data ) === false 
                ) &&
                //不能在 a 标签上触发click,否则可能就跳转页面了
                !(
                    type === "click" &&
                    jQuery.nodeName( elem, "a" )
                ) &&
                //可以绑定数据的元素,这个api在数据缓存章节介绍过了,忘记的可以回头看下,很好理解
                jQuery.acceptData( elem )
            ){
                //不在隐藏元素上触发focus/blur事件
                //不在window对象上触发默认行为
                if( 
                    ontype && 
                    elem[ type ] && 
                    ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && 
                    !jQuery.isWindow( elem ) 
                ){
                    // 如果设置过行内事件,在之前就已经被触发过了,
                    // 这里将其保存到临时变量中,然后清空,
                    // 然后再触发浏览器行内事件,以触发浏览器默认行为
                    // 然后再回复行内事件
                    old = elem[ ontype ];
                    if ( old )
                        elem[ ontype ] = null;
                    jQuery.event.triggered = type;
                    elem[ type ]();
                    jQuery.event.triggered = undefined;
                    if ( old )
                        elem[ ontype ] = old;
                }
            }
        }
        return event.result;
    },
    //...
}

在看过前几篇的事件处理后, 相信本篇并不难理解, 本篇就介绍到这里。
感谢您的阅读,欢迎留言:斧正,交流,提问;

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

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

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