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

本篇来介绍下关于jQ的事件触发机制:
当我们触发绑定事件时,本质上是使用了jQuery.event.dispatch(e)方法
我们来看回顾下载on章节中,提到的绑定事件到dom元素上的代码

jQuery.event = {
    //绑定事件
    add: function( elem, types, handler, data, selector ) {
        //...
        // 没有的话,先设置一个主监听函数
        if ( !eventHandle ) {
            //jQ的事件系统只会为元素分配一个主监听函数,
            //并且所有类型的事件在绑定时,只会绑定这个函数
            elemData.handle = eventHandle = function( e ) {
                return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
                    jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
                    undefined;
            };
            // 将dom元素绑定到事件的监听函数上
            eventHandle.elem = elem;
        }
        //...
    }
}

可以看出, 实际绑定到dom中, 被浏览器触发的函数, 是一个将dispatch()封装过后的函数,
下面小编来show下源码:
看之前小编先声明一点,这个api里的this指向的是触发的DOM元素(注意上面的apply哦)

jQuery.event = {
    //...
    dispatch: function( event ) {
        // 将原生的event的对象,包装为一个jQuery.Event对象
        // 这里做了些兼容性处理,将一些不兼容的参数进行了修正
        event = jQuery.event.fix( event || window.event );
            //处理事件的事件队列
        var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
            //事件委托的计数器
            delegateCount = handlers.delegateCount,
            //传入进来的参数的数组
            args = [].slice.call( arguments, 0 ),
            //手动 trigger 的事件如果有 ! 号,则 exclusive 为 true,表示只执行不带命名空间的fn
            //当 exclusive 为 false,并且事件没有指定命名空间时,则运行所有的事件
            run_all = !event.exclusive && !event.namespace,
            //查看事件是否有修正的操作对象
            special = jQuery.event.special[ event.type ] || {},
            //待执行队列
            handlerQueue = [],
            i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
        //修正后的event重新设置下
        args[0] = event;
        //设置 事件代理 的对象
        event.delegateTarget = this;

        if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
            return;
        }

        // 事件委托计数器>0 || (点击事件 && 鼠标左键) 
        if ( delegateCount && !(event.button && event.type === "click") ) {

            jqcur = jQuery(this);
            jqcur.context = this.ownerDocument || this;

            //这里的for的目的是 从目标元素一直遍历到自身元素,然后同时遍历每个代理对象的对象
            //来匹配下对应的事件处理元素又哪些
            for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
                // 不触发隐藏元素的事件
                if ( cur.disabled !== true ) {
                    selMatch = {};
                    matches = [];
                    jqcur[0] = cur;
                    for ( i = 0 ; i < delegateCount ; i++ ) {
                        handleObj = handlers[ i ];
                        //委托事件的匹配串
                        sel = handleObj.selector;
                        //存储dom元素是否是每个函数要处理的dom
                        if ( selMatch[ sel ] === undefined ) {
                            selMatch[ sel ] = (
                                //如果函数存在quick,则直接调用quickIs的匹配,否则调用$().is()的匹配api
                                //is可以返回元素是否可以被sel匹配上
                                handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
                            );
                        }
                        //如果元素被匹配上,则把函数追加到队列中
                        if ( selMatch[ sel ] ) {
                            matches.push( handleObj );
                        }
                    }
                    if ( matches.length ) {
                        //加入到待执行的队列中 { 触发元素, 触发函数数组 }
                        handlerQueue.push({ elem: cur, matches: matches });
                    }
                }
            }
        }

        // 如果事件队列数组的长度大于委托计数器,则将剩余的函数都追加到待触发事件的末尾
        if ( handlers.length > delegateCount ) {
            handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
        }

        // 先执行事件委托,这样可以通过 stopPropagation 来阻止事件的传播
        for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
            matched = handlerQueue[ i ];
            //代理事件可以通过这个属性来获取代理的元素
            event.currentTarget = matched.elem;

            for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
                handleObj = matched.matches[ j ];

                // Triggered event must either 
                // 1) be non-exclusive and have no namespace, or
                // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
                if( run_all || 
                    (!event.namespace && !handleObj.namespace) || 
                    event.namespace_re && 
                    event.namespace_re.test( handleObj.namespace ) 
                ){
                    //用户绑定的自定义数据
                    event.data = handleObj.data;
                    event.handleObj = handleObj;
                    //触发调用
                    ret = ( 
                        (jQuery.event.special[ handleObj.origType ] || {}).handle ||
                        handleObj.handler 
                    ).apply( matched.elem, args );

                    if ( ret !== undefined ) {
                        //保存运行结果
                        event.result = ret;
                        //如果返回了false,
                        //则调用 阻止默认事件 && 阻止事件冒泡
                        if ( ret === false ) {
                            event.preventDefault();
                            event.stopPropagation();
                        }
                    }
                }
            }
        }
        // 执行特殊的触发操作
        if ( special.postDispatch ) {
            special.postDispatch.call( this, event );
        }
        return event.result;
    }
}

本篇的逻辑性还是很顺序化的, 没有跳跃性, 就不多解释了。

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

评论
发表评论
4年前

@一只年老的橙子 恩恩,(*^__^*) ,3Q, 不好意思,后面的有些难懂,jQ越后面的功能和前面联系的越紧密,在下有事也得回复下之前的 O(∩_∩)O哈, 以后速度会保持哒,这个系列在下一定会坚持到底,感谢阅读

4年前

支持!小编最近更新有点慢哦。。

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

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

我的收藏