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

本篇来介绍下关于 jQ 绑定事件的流程:
先来看下对外的api :
$().click(); $().change(); ...;
$().on(); $().one(); $().bind(); $().delegate();
以上这些绑定最终都会执行到 $().on() 方法中,
先来看下结构:

jQuery.fn.extend({
    on: function( types, selector, data, fn, one ){
        //....
        jQuery.event.add(/*一堆参数*/);
    },
    one: function( types, selector, data, fn ) {
        return this.on( types, selector, data, fn, 1 );
    },
    bind: function( types, data, fn ) {
        return this.on( types, null, data, fn );
    },
    delegate: function( selector, types, data, fn ) {
        return this.on( types, selector, data, fn );
    },
});

有人可能会奇怪了,click神马的没有列出?是的,那个小编放到了后边,
小编先告诉您,在那些快捷方法(暂且先这么称呼吧)中,也是调用了 $().on();方法

jQuery.fn.extend({
    //绑定事件
    //selector指的是要代理的目标元素,delegate,on,one,均有体现
    on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
        var origFn, type;
        if ( typeof types === "object" ) {
            // ( types-Object, selector, data )
            if ( typeof selector !== "string" ) { 
                // ( types-Object, data )
                data = data || selector;
                selector = undefined;
            }
            for ( type in types ) {
                // { click: fn, mouseover: fn }
                this.on( type, selector, data, types[ type ], one );
            }
            return this;
        }
        //以下是针对各种调用情况的参数处理
        //jQ通过参数的顺序和类型的检测,以分辨各种调用情况。
        if ( data == null && fn == null ) {
            // ( types, fn )
            fn = selector;
            data = selector = undefined;
        } else if ( fn == null ) {
            if ( typeof selector === "string" ) {
                // ( types, selector, fn )
                fn = data;
                data = undefined;
            } else {
                // ( types, data, fn )
                fn = data;
                data = selector;
                selector = undefined;
            }
        }
        //如果处理方法是false,则设置为 fn:return false;
        if( fn === false ){
            // function returnFalse(){ return false; };
            fn = returnFalse;
        //如果没有传入处理函数,也就不用后续了
        }else if( !fn ){
            return this;
        }
        //如果是one的情况,则将fn封装为一个只执行一次的函数
        if ( one === 1 ) {
            origFn = fn;
            //在事件触发时,先进行移除事件函数,然后在执行
            fn = function( event ) {
                jQuery().off( event );
                return origFn.apply( this, arguments );
            };
            //设置函数的唯一标示,fn,origFn是同一个函数,所以这里要设置一个相同的标示
            fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
        }
        return this.each( function() {
            jQuery.event.add( this, types, fn, data, selector );
        });
    },
    //...
});

可以看到在on方法的末尾,调用了jQuery.event.add,
而这是真实绑定到dom上事件的处理,来看下代码吧

  jQuery.event = {
        // 绑定一个或多个类型的事件监听函数
        add: function( elem, types, handler, data, selector ) {
            var elemData, eventHandle, events,
                t, tns, type, namespaces, handleObj,
                handleObjIn, quick, handlers, special;
            // 文本节点 || 注释节点 || 参数残缺不全 || 元素不支持绑定属性
            // jQuery._data(elem): 如果元素不支持则 return ; 支持的则 return object; 没有数据也会 return {};
            if( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || 
                !(elemData = jQuery._data( elem )) ){
                return;
            }
            // handler可以是一个自定义监听对象
            // 当handler是对象时,从中取出以下这些参数,这个在后面的 cloneCopyEvent 函数中会有应用
            // 这里大家可以忽略这个if,像后看
            if ( handler.handler ) {
                handleObjIn = handler;
                handler = handleObjIn.handler;
                selector = handleObjIn.selector;
            }
            // 为每个, 没有标示的函数, 分配一个标示
            // 这个标示将会用于移除函数
            if ( !handler.guid ) {
                handler.guid = jQuery.guid++;
            }
            // 取出存储事件的容器,还没有则重新赋值
            events = elemData.events;
            if ( !events ) {
                elemData.events = events = {};
            }
            // 取出主监听函数
            eventHandle = elemData.handle;
            // 没有的话,先设置一个主监听函数
            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;
            }
            //将"hover"拆分成"mouseenter mouseleave".split;
            types = jQuery.trim( hoverHack(types) ).split( " " );
            for ( t = 0; t < types.length; t++ ) {
                // 'click.namespace','click','namespace'
                tns = rtypenamespace.exec( types[t] ) || [];
                type = tns[1];
                // 命名空间可以指定多个, 以 . 分开
                namespaces = ( tns[2] || "" ).split( "." ).sort();
                // 查找事件的一些修正:绑定,代理,触发,移除
                /*
                    特别声明:
                        后续的代码中,将会频繁的看到 special 关键词
                        这是一个修正对象,就像以前在设置属性的时候,部分属性需要进行特殊的处理,
                        进而映射成 hooks 对象,在本系列的最后小编将会整理好有特殊处理的各个事件
                        暂且,如果有对special对象的操作不理解的,可以先跳过,
                        就假定全部相关的if判断全部通过即可
                */
                special = jQuery.event.special[ type ] || {};
                // 如果 selector 存在,则启用事件代理,需要把不冒泡的事件, 修正为可以冒泡的
                // 不传入 selector , 则将 不支持或者支持的不完整的修正为之支持度更好的事件
                type = ( selector ? special.delegateType : special.bindType ) || type;
                // 到这里时也许type是被修正过的了,所以这里要再次取一下修正的对象
                special = jQuery.event.special[ type ] || {};
                // 这里是对以上的元素综合整理后的 处理对象
                handleObj = jQuery.extend({
                    //修正完的eventType
                    type: type,
                    //初始化时传入的eventType
                    origType: tns[1],
                    //函数触发时可以传入的自定义参数
                    data: data,
                    //监听处理函数
                    handler: handler,
                    //处理函数的标示
                    guid: handler.guid,
                    //事件代理时指定的子对象
                    selector: selector,
                    //这里是对selector字符串的进一步解析 以便后更快捷的使用
                    //"div#con"=> [ "div#con", "div", "con", undefined" ]
                    //"div.cla"=> [ "div.cla", "div", undefined, /(?:^|\s)cla(?:\s|$)/ ]
                    // 1=>elem, 2=>id, 3=>classReg
                    quick: selector && quickParse( selector ),
                    //命名空间,此时是排序后的
                    namespace: namespaces.join(".")
                }, handleObjIn );
                // 去事件存储对象中取出事件队列
                handlers = events[ type ];
                if ( !handlers ) {
                    //这里对type类型的队列进行初始化
                    handlers = events[ type ] = [];
                    //初始化计数器
                    //用于指向哪个函数是委托的
                    handlers.delegateCount = 0;
                    // 修正对象里要是存在setup则先调用setup,返回false则继续执行绑定
                    if( !special.setup || 
                        special.setup.call( elem, data, namespaces, eventHandle ) === false ){
                        // 绑定事件
                        if( elem.addEventListener ){
                            elem.addEventListener( type, eventHandle, false );
                        }else if( elem.attachEvent ){
                            elem.attachEvent( "on" + type, eventHandle );
                        }
                    }
                }
                if ( special.add ){
                    special.add.call( elem, handleObj );
                    if ( !handleObj.handler.guid ) {
                        handleObj.handler.guid = handler.guid;
                    }
                }
                /*
                    这里有点意思,如果本次绑定的是 委托,
                    则将他插入到队列的头部,使用一个计数器来进行和普通函数进行区别
                    0  -  delegateCount => 委托事件
                    delegateCount - end => 普通事件
                */
                // 事件委托
                if ( selector ) {
                    //将委托函数放插入到 函数队列中,handlers.delegateCount指向的位置
                    handlers.splice( handlers.delegateCount++, 0, handleObj );
                //普通处理函数,直接插入队列
                } else {
                    handlers.push( handleObj );
                }
                // 做个标记,表示该类型的事件被绑定过
                jQuery.event.global[ type ] = true;
            }
            // 释放内存
            elem = null;
        },
        //...
    };

本篇就介绍到这里,总结下:
        通过很多形式开放的api方便用户的各种绑定操作 ↓
        处理各种情况的参数,整理完毕后调用统一的内部处理函数 ↓
        将数据进行缓存,同时绑定到真实DOM元素上 ↓
        返回this(jQ对象)

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

评论
发表评论
3年前
添加了一枚【评注】:111
WRITTEN BY
前端狮子
JS前端开发工程师 :喜欢研究js,nodejs,html5; 希望结交更多朋友~
TA的新浪微博
PUBLISHED IN
# 菜鸟解读 jQuery #

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

我的收藏