jQuery的Event模块提供了强大的功能:事件代理,自定义事件,自定义数据等。今天记录一下它实现的原理。

  我们都知道,在js的原生事件中,有事件对象和回调函数这两样东西。但是事件对象是只读的,所以jQuery就用了自己的Event对象替代了原生的事件对象,这样就可以实现对事件对象的完全控制,所以才能实现自定义数据。而回调函数的话,每个元素只有一个一样的回调函数,这样方便管理。

  1. 下面来看看event对象长什么样。


    可以看到jQuery的事件对象其实一开始就只有这么一点东西。
    其中originalEvent是原生事件对象副本。
    jQuery211030632698768749833则是一个标志,以后可以用这个标志来判断这个对象是不是jQuery的事件对象。

  2. 紧接着我们看一下Event对象的原型。

    可以看到有六个个方法,前三个是用来判断是否已经被阻止默认行为、是否已经被阻止冒泡和默认行为、是否已经被阻止冒泡。
    后三个则是相应的操作。
    上源代码:
    jQuery.Event = function( src, props ) {//src可以是原生事件类型、jquery事件类型、自定义事件类型、原生事件对象
    // Allow instantiation without the 'new' keyword 不用new关键字实例化jquery的事件对象
    if ( !(this instanceof jQuery.Event) ) {
    return new jQuery.Event( src, props );
    } // Event object
    if ( src && src.type ) {//如果是原生事件对象或jquery事件类型
    this.originalEvent = src;//保存原生事件对象
    this.type = src.type;//事件类型 // Events bubbling up the document may have been marked as prevented
    // by a handler lower down the tree; reflect the correct value.
    this.isDefaultPrevented = src.defaultPrevented ||//是否被更底层的事件阻止默认行为
    src.defaultPrevented === undefined &&
    // Support: Android < 4.0
    src.returnValue === false ?
    returnTrue :
    returnFalse; // Event type
    } else {//原生事件类型、自定义事件类型
    this.type = src;
    } // Put explicitly provided properties onto the event object
    if ( props ) {//如果传入了自定义的props对象,将其复制到jQuery.Event对象上
    jQuery.extend( this, props );
    } // Create a timestamp if incoming event doesn't have one
    this.timeStamp = src && src.timeStamp || jQuery.now();//加时间戳 // Mark it as fixed
    this[ jQuery.expando ] = true;//jQuery.expando是页面中每一个jQuery副本唯一的标志。此属性来判断当前事件对象是否为jQuery事件对象
    }; // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
    // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
    jQuery.Event.prototype = {
    isDefaultPrevented: returnFalse,//是否已经阻止默认行为
    isPropagationStopped: returnFalse,//是否已经阻止事件传播
    isImmediatePropagationStopped: returnFalse,//是否已经阻止事件执行和事件传播 preventDefault: function() {
    var e = this.originalEvent; this.isDefaultPrevented = returnTrue; if ( e && e.preventDefault ) {
    e.preventDefault();
    }
    },
    stopPropagation: function() {
    var e = this.originalEvent; this.isPropagationStopped = returnTrue; if ( e && e.stopPropagation ) {
    e.stopPropagation();
    }
    },
    stopImmediatePropagation: function() {
    var e = this.originalEvent; this.isImmediatePropagationStopped = returnTrue; if ( e && e.stopImmediatePropagation ) {
    e.stopImmediatePropagation();
    } this.stopPropagation();
    }
    };

    可以看到,jQuery用构造函数来创建对象,并且用prototype原型来继承公有的方法。
    但是jQuery事件对象还没就此就结束了。因为还需要把像target这些有用的事件属性从原生的事件对象复制过来。这就是工具方法jQuery.event.fix()的作用了。
    看一下经过fix函数之后Event对象变成了什么样子。

    可以看到,应该有的属性都有了。这里的fix还做了一些兼容性的事情。
    在键盘事件的时候,只是按下按键按钮的keycode和charcode在不同浏览器下的表现不一样。所以jQuery统一用一个which属性来指示。
    另外在鼠标事件的时候,因为有个button属性,该属性是记录按下鼠标按钮的。但是ie和dom标准不一样。统一把它修正,并用which来记录。
    还有一个就ie低版本不支持pageX和pageY的情况。
    上代码:

    props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
    
        fixHooks: {},
    
        keyHooks: {
    props: "char charCode key keyCode".split(" "),
    filter: function( event, original ) { // Add which for key events
    if ( event.which == null ) {
    event.which = original.charCode != null ? original.charCode : original.keyCode;
    } return event;
    }
    }, mouseHooks: {
    props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
    filter: function( event, original ) {
    var eventDoc, doc, body,
    button = original.button; // Calculate pageX/Y if missing and clientX/Y available
    if ( event.pageX == null && original.clientX != null ) {
    eventDoc = event.target.ownerDocument || document;
    doc = eventDoc.documentElement;
    body = eventDoc.body; event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
    event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
    } // Add which for click: 1 === left; 2 === middle; 3 === right
    // Note: button is not normalized, so don't use it
    if ( !event.which && button !== undefined ) {
    event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
    } return event;
    }
    }, fix: function( event ) {//event可以为jQuery对象或者原生事件对象 复制事件对象属性,并修正特殊的
    if ( event[ jQuery.expando ] ) {//判断是否为jQuery事件对象
    return event;
    } // Create a writable copy of the event object and normalize some properties
    var i, prop, copy,
    type = event.type,
    originalEvent = event,
    fixHook = this.fixHooks[ type ];//用于存放键盘和鼠标事件的不兼容属性,fixhooks初始值为空对象 if ( !fixHook ) {//rkeyEvent = /^key/,rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
    this.fixHooks[ type ] = fixHook =
    rmouseEvent.test( type ) ? this.mouseHooks :
    rkeyEvent.test( type ) ? this.keyHooks :
    {};
    }
    copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;//存放所有属性的副本 event = new jQuery.Event( originalEvent );//创建jQuery事件对象 i = copy.length;
    while ( i-- ) {//把原生属性和修正后的不兼容的属性复制到jQuery事件对象中
    prop = copy[ i ];
    event[ prop ] = originalEvent[ prop ];
    } // Support: Cordova 2.5 (WebKit) (#13255)
    // All events should have a target; Cordova deviceready doesn't
    if ( !event.target ) {
    event.target = document;
    } // Support: Safari 6.0+, Chrome < 28
    // Target should not be a text node (#504, #13143)
    if ( event.target.nodeType === 3 ) {//修正Safari 6.0+, Chrome < 28中event.target为文本节点的情况
    event.target = event.target.parentNode;
    } return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;//修正鼠标事件和键盘事件的专属属性;键盘的按键所对应的编码,和鼠标的clientX、鼠标编码
    },
  3. 接下来是处理函数了。每个元素只有一个处理函数。可以说就是dispatch()。为了配合这个处理函数的工作,还有一个对象,这个对象的每个元素是一个存放不同类型事件处理函数的数组。这个数组中存放着所有的代理事件和自身的事件。先来看看jQuery是如何把处理函数放进这个数组的。jQuery中快捷事件函数click这一些会调用on函数,而on函数又会调用工具函数jQuery.event.add()来绑定事件。所以在jQuery中所有的事件都是通过这个add函数来绑定的。
    下面说说add函数。
    add函数主要是把处理函数处理成一个对象,并把这个对象推入到处理函数对象数组的合适位置。
    这个对象长这个样子:

    其中,data是我们自定义的数据。
    handler使我们传进去的处理函数
    type是事件类型,
    origType是我们传进去的事件类型,
    selector是事件代理的选择器。
    在这里为什么会有个type和一个origType呢?这是因为有些事件类型不好控制,所以就会拿别的事件类型来代替和模拟。这些事件有:
    focus/blur因为不支持事件冒泡,所以会用focusein/focusout来代替。

    mouseenter: "mouseover",
    mouseleave: "mouseout",
    pointerenter: "pointerover",
    pointerleave: "pointerout"   在这些中,前面的会用后面的来代替。因为在由父元素进入子元素时重复触发事件的问题。

    那这些处理函数对象入数组有个什么样的顺序呢。其实就是先来的在前,后来的在后,代理事件在前。
    看下面例子:

    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Event fun</title>
    </head>
    <body>
    <div style="width:200px;height:600px;background-color: red;">
    <div style="width:200px;height:500px;background-color: blue;">
    <div style="width:200px;height:400px;background-color: green;">
    <div id="a" style="width:200px;height:300px;background-color: black;">
    <div style="width:200px;height:200px;background-color: yellow;"> </div>
    </div>
    </div>
    </div>
    </div>
    <script src="../../jquery-2.1.1.js"></script>
    <script>
    $(document).on('click',function(){})
    $(document).on('click','#a',function(){})
    $(document).on('click','div',{
    name:'qq',
    age:'dd'
    },function(){
    console.log(1);
    })
    var doc = $(document)
    console.log(document.events.click)
    // document.onclick = function(e){
    // console.log(1);
    // }
    </script> </body>
    </html>

    生成的数组对象如下图。


    上代码:

    add: function( elem, types, handler, data, selector ) {//将回调函数插入响应数组
    
            var handleObjIn, eventHandle, tmp,
    events, t, handleObj,
    special, handlers, type, namespaces, origType,
    elemData = data_priv.get( elem ); // Don't attach events to noData or text/comment nodes (but allow plain objects)
    if ( !elemData ) {//当前元素不支持附加扩展属性
    return;
    } // Caller can pass in an object of custom data in lieu of the handler
    if ( handler.handler ) {//自定义监听对象的情况
    handleObjIn = handler;
    handler = handleObjIn.handler;
    selector = handleObjIn.selector;
    } // Make sure that the handler has a unique ID, used to find/remove it later
    if ( !handler.guid ) {//确定有唯一的id
    handler.guid = jQuery.guid++;
    } // Init the element's event structure and main handler, if this is the first
    if ( !(events = elemData.events) ) {//如果事件缓存对象不存在,则初始化.用于存储事件对象
    events = elemData.events = {};
    }
    if ( !(eventHandle = elemData.handle) ) {//取出或初始化主监听函数
    eventHandle = elemData.handle = function( e ) {//丢弃jQuery.event.trigger()第二个事件和页面关闭后触发的事件
    // Discard the second event of a jQuery.event.trigger() and
    // when an event is called after a page has unloaded
    return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
    jQuery.event.dispatch.apply( elem, arguments ) : undefined;
    };
    } // Handle multiple events separated by a space
    types = ( types || "" ).match( rnotwhite ) || [ "" ];//分解多事件
    t = types.length;
    while ( t-- ) {
    tmp = rtypenamespace.exec( types[t] ) || [];//分解事件
    type = origType = tmp[1];//单个事件类型
    namespaces = ( tmp[2] || "" ).split( "." ).sort();//分解命名空间数组 // There *must* be a type, no attaching namespace-only handlers
    if ( !type ) {//忽略只有命名空间的情况
    continue;
    } // If event changes its type, use the special event handlers for the changed type
    special = jQuery.event.special[ type ] || {};//尝试获取当前事件类型对应的修正对象 // If selector defined, determine special event api type, otherwise given type
    type = ( selector ? special.delegateType : special.bindType ) || type;//修正type,如果有selector修正为代理事件,或者支持更好的类型 // Update special based on newly reset type
    special = jQuery.event.special[ type ] || {};//type可能已经改变,所以尝试再次获取修正对象 // handleObj is passed to all event handlers
    handleObj = jQuery.extend({//把监听函数封装成监听对象
    type: type,//修正后的事件类型
    origType: origType,//单个原始事件类型
    data: data,//传入的附加对象
    handler: handler,//监听函数
    guid: handler.guid,//函数id
    selector: selector,//代理绑定时的过滤选择器
    needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
    namespace: namespaces.join(".")//原始命名空间
    }, handleObjIn ); // Init the event handler queue if we're the first
    if ( !(handlers = events[ type ]) ) {//第一次绑定该类型事件时,初始化监听对象数组
    handlers = events[ type ] = [];
    handlers.delegateCount = 0;//下一个代理监听对象的插入位置 // Only use addEventListener if the special events handler returns false
    if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {//优先使用修正对象的修正方法绑定主监听函数
    if ( elem.addEventListener ) {
    elem.addEventListener( type, eventHandle, false );
    }
    }
    }
    //将监听对象插入对象数组
    if ( special.add ) {//修正对象有修正方法add,用add
    special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) {
    handleObj.handler.guid = handler.guid;
    }
    } // Add to the element's handler list, delegates in front
    if ( selector ) {//将代理监听对象插入到指定位置
    handlers.splice( handlers.delegateCount++, 0, handleObj );
    } else {//非代理的插入末尾
    handlers.push( handleObj );
    } // Keep track of which events have ever been used, for event optimization
    jQuery.event.global[ type ] = true;//记录绑定过的事件类型
    } }, //修正事件的代码。
    // Create mouseenter/leave events using mouseover/out and event-time checks
    // Support: Chrome 15+
    jQuery.each({//修正这四个事件的处理函数
    mouseenter: "mouseover",
    mouseleave: "mouseout",
    pointerenter: "pointerover",
    pointerleave: "pointerout"
    }, function( orig, fix ) {
    jQuery.event.special[ orig ] = {
    delegateType: fix,
    bindType: fix, handle: function( event ) {
    var ret,
    target = this,
    related = event.relatedTarget,
    handleObj = event.handleObj; // For mousenter/leave call the handler if related is outside the target.
    // NB: No relatedTarget if the mouse left/entered the browser window
    if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
    event.type = handleObj.origType;
    ret = handleObj.handler.apply( this, arguments );
    event.type = fix;
    }
    return ret;
    }
    };
    }); // Create "bubbling" focus and blur events
    // Support: Firefox, Chrome, Safari
    if ( !support.focusinBubbles ) {//修正focus/blur的处理函数。和特殊的主监听函数的添加和删除(因为不支持冒泡)
    jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler on the document while someone wants focusin/focusout
    var handler = function( event ) {
    jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
    }; jQuery.event.special[ fix ] = {
    setup: function() {
    var doc = this.ownerDocument || this,
    attaches = data_priv.access( doc, fix ); if ( !attaches ) {
    doc.addEventListener( orig, handler, true );
    }
    data_priv.access( doc, fix, ( attaches || 0 ) + 1 );
    },
    teardown: function() {
    var doc = this.ownerDocument || this,
    attaches = data_priv.access( doc, fix ) - 1; if ( !attaches ) {
    doc.removeEventListener( orig, handler, true );
    data_priv.remove( doc, fix ); } else {
    data_priv.access( doc, fix, attaches );
    }
    }
    };
    });
    }

jQuery源码分析--Event模块(1)的更多相关文章

  1. jQuery源码分析--Event模块(2)

    接下来就是触发事件了.事件触发后的处理函数的分发主要靠两个函数,一个jQuery.event.dispatch,一个是jQuery.event.handlers.这个dispatch会调用handle ...

  2. jQuery源码分析--Event模块(3)

    最后剩下了事件的手动触发了.jQuery提供了两个函数trigger和triggerHandler来手动触发事件,可以触发原生事件和自定义的事件.这个触发不单只会触发有jQuery绑定事件,而且也会触 ...

  3. Zepto源码分析-event模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  4. nginx源码分析——event模块

    源码:nginx 1.12.0   一.简介      nginx是一款非常受欢迎的软件,具备高性能.模块化可定制的良好特性.之前写了一篇nginx的http模块分析的文章,主要对http处理模块进行 ...

  5. zepto源码分析·event模块

    准备知识 事件的本质就是发布/订阅模式,dom事件也不例外:先简单说明下发布/订阅模式,dom事件api和兼容性 发布/订阅模式 所谓发布/订阅模式,用一个形象的比喻就是买房的人订阅楼房消息,售楼处发 ...

  6. 读Zepto源码之Event模块

    Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...

  7. [转] jQuery源码分析-如何做jQuery源码分析

    jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...

  8. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  9. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

随机推荐

  1. capitalize()

    capitalize() 是字符串的一个方法,用于把字符串的第一个字母转换成大写 In [1]: str = 'hello world' In [2]: str.capitalize() Out[2] ...

  2. CentOS-6.3安装配置Nginx--【测试已OK】

    安装说明 系统环境:CentOS-6.3软件:nginx-1.2.6.tar.gz安装方式:源码编译安装 安装位置:/usr/local/nginx 下载地址:http://nginx.org/en/ ...

  3. Python-Numpy的tile函数用法

    1.函数的定义与说明 函数格式tile(A,reps) A和reps都是array_like A的类型众多,几乎所有类型都可以:array, list, tuple, dict, matrix以及基本 ...

  4. MQTT的学习研究(十六) MQTT的Mosquitto的window安装部署

    在mqtt的官方网站,有许多mqtt,其中:MosquittoAn Open Source MQTT server with C, C++, Python and Javascript clients ...

  5. oracle如何四舍五入?

    转自:http://www.jb51.net/article/84924.htm 取整(向下取整): 复制代码代码如下: select floor(5.534) from dual;select tr ...

  6. linux的~和/的区别

    转自:https://zhidao.baidu.com/question/166486946.html /是目录层的分隔.表示符.只有一个/表明是root,/etc/表明是根目录下面的etc目录(当然 ...

  7. OC开发_Storyboard——Core Data

    一 .NSManagedObjectContext 1.我们要想操作Core Data,首先需要一个NSManagedObjectContext2.那我们如何获得Context呢:创建一个UIMana ...

  8. 收集一些常用的CDN链接!无需下载快速使用!

    一些常用的CDN链接,可以到这里看: http://www.bootcdn.cn/ 这个网站查找资源的方式很简单,后缀加上要查找的名字即可: 例如: http://www.bootcdn.cn/boo ...

  9. Java定时器和Quartz使用

    一.Java普通自定义定时器 /** * 自定义一个定时器 * @author lw */ public class MyTimer extends Thread{ private Long time ...

  10. mysql-sql高级应用

    sql语言进阶 典型操作 order by - select * from play_list order by createtime; - select * from play_list order ...