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

在前几章中,多次提到过有关 special[eventType] 对象,
本章来解释下各种事件的各种修正:
先来简单回顾下关于特殊对象的出现场景:

jQuery.event = {
    add: function(){
        //... jQuery.event.special[ type ]
        if ( !special.setup || 
            special.setup.call( elem, data, namespaces, eventHandle ) === false 
        ) { 
            //...
        }
        //...
    },
    remove: function(){
        //... jQuery.event.special[ type ]
        if ( special.remove ) {
            special.remove.call( elem, handleObj );
        }
        //...
    },
    trigger: function(){
        //... jQuery.event.special[ type ]
        if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
            return;
        }
        //...
    },
}

好了,想必大家也应该稍有印象了,下面小编来整体介绍下有关所有的修正操作,

jQuery.event = {
    /***************************************************************************

        对修正对象的整体梳理:
        onBubble    当前事件类型不支持或者不允许冒泡
        bindType    绑定普通事件时使用的事件类型
        delegateType    绑定代理事件时使用的事件类型
        setup   用于执行特殊的主监听函数的“绑定行为”或者绑定前的初始化操作,
            在绑定前被调用,如果返回false则继续调用原生方法绑定主监听函数   
            if ( !special.setup || 
                special.setup.call( elem, data, namespaces, eventHandle ) === false 
            ) {
                // addEventListener || attachEvent
            }
        teardown    同上,这是移除前需要调用的
            if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
                jQuery.removeEvent( elem, type, elemData.handle );
            }
        handle  执行事件响应行为,在每次出发当前类型的事件时调用
            ret = ( 
                (jQuery.event.special[ handleObj.origType ] || {}).handle ||
                handleObj.handler 
            ).apply( matched.elem, args );
        add     在处理事件绑定时被调用,该方法触发后,会继续执行正常的绑定流程
            if ( special.add ) {
                special.add.call( elem, handleObj );
            }   
        remove  在删除事件前调用
            if ( special.remove ) {
                special.remove.call( elem, handleObj );
            }
        trigger     触发当前类型事件时被调用
            if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
                return;
            }
        _default    执行特殊的默认行为,如果返回false则执行浏览器默认行为
            if( 
                (!special._default || 
                    special._default.apply( elem.ownerDocument, data ) === false 
                ) &&
                //....
            ){}

    ***************************************************************************/
    special: {
        ready: {
            // 这个事件总也不会返回false,对这个函数各位应该是很眼熟了,就不多解释了,
            // 不太清楚的可以参考小编写的第一篇
            setup: jQuery.bindReady
        },
        load: {
            // 该事件不允许冒泡
            // 实际上浏览器本身是不会冒泡的,但是在手动触发该事件时,
            // 在构造冒泡路径时,会检测这个属性,来判断是否执行冒泡模拟
            noBubble: true
        },
        // focus/blur浏览器是不会冒泡的,
        // 所以当用户要绑定事件代理时,换用focusin/focusout
        focus: {
            delegateType: "focusin"
        },
        blur: {
            delegateType: "focusout"
        },
        //beforeunload在页面刷新或关闭时触发
        //这个事件如果使用addEvent||attach进行绑定的话,ie9以下和ff中是不会被触发的
        //以下两种形式都可以跨浏览器的解决
        //  <body onbeforeunload="handler">
        //  window.onbeforeunload = handler;
        beforeunload: {
            setup: function( data, namespaces, eventHandle ) {
                if ( jQuery.isWindow( this ) ) {
                    this.onbeforeunload = eventHandle;
                }
            },
            teardown: function( namespaces, eventHandle ) {
                if ( this.onbeforeunload === eventHandle ) {
                    this.onbeforeunload = null;
                }
            }
        }
    },
    //...
}
// 初始化事件对应的修正: mouseenter, mouseleave
jQuery.each({
    mouseenter: "mouseover",
    mouseleave: "mouseout"
}, function( orig, fix ) {
    jQuery.event.special[ orig ] = {
        delegateType: fix,
        bindType: fix,
        handle: function( event ) {
            var target = this,
                //目标元素的关联元素
                related = event.relatedTarget,
                //事件处理对象
                handleObj = event.handleObj,
                //事件代理的字符串
                selector = handleObj.selector,
                ret;
            // jQuery.contains(a,b) 函数的作用是检测a是否包含b,包含就返回true,不包含就返回false
            // jQ通过检测 目标元素 和 关联元素 的关系,来决定是否触发over||out事情
            // 最终的效果是: 
            // 当从父元素移入到子元素时,不触发 父元素的mouseout事件 和 子元素冒泡上来的mouseover事件
            // 当从子元素移入到父元素时,不触发 父元素的mouseover事件 和 子元素冒泡到父元素的mouseout事件
            if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
                event.type = handleObj.origType;
                ret = handleObj.handler.apply( this, arguments );
                event.type = fix;
            }
            return ret;
        }
    };
});
// 在ie6,7,8下submit事件是不支持冒泡的,这里通过模拟来实现
if ( !jQuery.support.submitBubbles ) {
    jQuery.event.special.submit = {
        //绑定dom前被执行..
        setup: function() {
            // 如果当前元素是表单元素,就当成普通事件帮就可以了
            if ( jQuery.nodeName( this, "form" ) ) {
                return false;
            }
            // 为当前元素绑定两个事件:带命名空间 "_submit" 的,
            jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
                var elem = e.target,
                    form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
                //如果元素还没有绑定过 submit._submit 事件,则绑定一次
                if ( form && !form._submit_attached ) {
                    jQuery.event.add( form, "submit._submit", function( event ) {
                        event._submit_bubble = true;
                    });
                    form._submit_attached = true;
                }
            });
            //没有返回,false,执行后续的正常的绑定逻辑
        },
        //事件触发后执行
        postDispatch: function( event ) {
            if ( event._submit_bubble ) {
                delete event._submit_bubble;
                if ( this.parentNode && !event.isTrigger ) {
                    //模拟事件submit,这里可以暂时理解为模拟了事件冒泡,具体的下章介绍
                    jQuery.event.simulate( "submit", this.parentNode, event, true );
                }
            }
        },
        //卸载事件前调用
        teardown: function() {
            if ( jQuery.nodeName( this, "form" ) ) {
                return false;
            }
            //删除_submit空间下的所有事件
            jQuery.event.remove( this, "._submit" );
        }
    };
}
// 在ie6,7,8下,change事件是不支持冒泡的,
if ( !jQuery.support.changeBubbles ) {
    jQuery.event.special.change = {
        setup: function() {
            //rformElems = /^(?:textarea|input|select)$/i
            //元素是 textarea input select 时,不需要代理,只需执行正常绑定即可
            if ( rformElems.test( this.nodeName ) ) {
                //在ie6,7,8下checkbox和radio要等到失去焦点时才会触发hange,
                //其他浏览器在点击的时候就会触发,所以这里通过模拟来进行触发
                if ( this.type === "checkbox" || this.type === "radio" ) {
                    jQuery.event.add( this, "propertychange._change", function( event ) {
                        //这里进行检测的原因是因为如果使用setAttribute()时,也会触发改事件,
                        //而这里对设置的属性值进行检测,当为checked时,执行即可
                        //这个会在click之前触发
                        if ( event.originalEvent.propertyName === "checked" ){
                            this._just_changed = true;
                        }
                    });
                    jQuery.event.add( this, "click._change", function( event ){
                        //触发点击事件时,如果是发生了对元素的点击,并且不是手动触发,
                        //则模拟change事件
                        if ( this._just_changed && !event.isTrigger ){
                            this._just_changed = false;
                            jQuery.event.simulate( "change", this, event, true );
                        }
                    });
                }
                //其他匹配成功的元素就不做操作了,返回即可
                return false;
            }
            //当鼠标按下的时候会触发,并且是在元素获得焦点前,改事件支持冒泡,ie支持
            jQuery.event.add( this, "beforeactivate._change", function( e ) {
                var targetElem = e.target;
                //在获取焦点前,如果事件源是表单元素,则为其绑定change事件
                if ( rformElems.test( targetElem.nodeName ) && !targetElem._change_attached ) {
                    jQuery.event.add( targetElem, "change._change", function( event ) {
                        //这个事件在被触发时,将模拟change事件
                        if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
                            jQuery.event.simulate( "change", this.parentNode, event, true );
                        }
                    });
                    //目标元素设定为true,以后不必再绑了
                    targetElem._change_attached = true;
                }
            });
        },
        //触发事件时调用
        handle: function( event ) {
            var elem = event.target;
            //
            if( this !== elem || 
                event.isSimulated || 
                event.isTrigger || 
                (elem.type !== "radio" && elem.type !== "checkbox") 
            )
                //触发真实事件
                return event.handleObj.handler.apply( this, arguments );
        },
        teardown: function() {
            jQuery.event.remove( this, "._change" );
            return rformElems.test( this.nodeName );
        }
    };
}
// focusin,focusout支持的并不完善,所以这里使用了模拟的方式来
if ( !jQuery.support.focusinBubbles ) {
    jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
        var attaches = 0,
            //主监听函数,在捕捉阶段被触发,手动模拟冒泡情况
            handler = function( event ) {
                jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
            };
        jQuery.event.special[ fix ] = {
            setup: function() {
                //这里会一直递增,但是只会在最初绑定一次事件
                if ( attaches++ === 0 ) {
                    //第三个参数绑定的是true哦
                    document.addEventListener( orig, handler, true );
                }
            },
            // 当绑定或代理的事件全部被移除后,
            // 再移除document上的focus,blur事件绑定的捕捉阶段的主监听函数
            teardown: function() {
                //这里会一直递减,但只会在最后一次解除绑定函数
                if ( --attaches === 0 ) {
                    document.removeEventListener( orig, handler, true );
                }
            }
        };
    });
}

本篇就介绍到这里。
感谢您的阅读,欢迎留言:斧正,交流,提问;

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

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

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