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

  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 objectif ( 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.0src.returnValue === false ?returnTrue :returnFalse;// Event type} else {//原生事件类型、自定义事件类型this.type = src;}// Put explicitly provided properties onto the event objectif ( props ) {//如果传入了自定义的props对象,将其复制到jQuery.Event对象上jQuery.extend( this, props );}// Create a timestamp if incoming event doesn't have onethis.timeStamp = src && src.timeStamp || jQuery.now();//加时间戳   // Mark it as fixedthis[ 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 eventsif ( 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 availableif ( 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 itif ( !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 propertiesvar 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'tif ( !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 handlerif ( handler.handler ) {//自定义监听对象的情况handleObjIn = handler;handler = handleObjIn.handler;selector = handleObjIn.selector;}// Make sure that the handler has a unique ID, used to find/remove it laterif ( !handler.guid ) {//确定有唯一的idhandler.guid = jQuery.guid++;}// Init the element's event structure and main handler, if this is the firstif ( !(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 unloadedreturn typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?jQuery.event.dispatch.apply( elem, arguments ) : undefined;};}// Handle multiple events separated by a spacetypes = ( 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 handlersif ( !type ) {//忽略只有命名空间的情况continue;}// If event changes its type, use the special event handlers for the changed typespecial = jQuery.event.special[ type ] || {};//尝试获取当前事件类型对应的修正对象// If selector defined, determine special event api type, otherwise given typetype = ( selector ? special.delegateType : special.bindType ) || type;//修正type,如果有selector修正为代理事件,或者支持更好的类型// Update special based on newly reset typespecial = jQuery.event.special[ type ] || {};//type可能已经改变,所以尝试再次获取修正对象// handleObj is passed to all event handlershandleObj = jQuery.extend({//把监听函数封装成监听对象type: type,//修正后的事件类型origType: origType,//单个原始事件类型data: data,//传入的附加对象handler: handler,//监听函数guid: handler.guid,//函数idselector: selector,//代理绑定时的过滤选择器needsContext: selector && jQuery.expr.match.needsContext.test( selector ),namespace: namespaces.join(".")//原始命名空间
                }, handleObjIn );// Init the event handler queue if we're the firstif ( !(handlers = events[ type ]) ) {//第一次绑定该类型事件时,初始化监听对象数组handlers = events[ type ] = [];handlers.delegateCount = 0;//下一个代理监听对象的插入位置// Only use addEventListener if the special events handler returns falseif ( !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 frontif ( selector ) {//将代理监听对象插入到指定位置handlers.splice( handlers.delegateCount++, 0, handleObj );} else {//非代理的插入末尾
                    handlers.push( handleObj );}// Keep track of which events have ever been used, for event optimizationjQuery.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 windowif ( !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/focusoutvar 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 );}}};});
    }

     

转载于:https://www.cnblogs.com/dq-Leung/p/4339652.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/398388.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JSP内置对象(9个常用的内置对象)

为什么80%的码农都做不了架构师&#xff1f;>>> 2012-08-06 1.request对象 客户端的请求信息被封装在request对象中&#xff0c;通过它才能了解到客户的需求&#xff0c;然后做出响应。它是HttpServletRequest类的实例。 序号 方 法 说 明 1 object getAttribute(S…

用户可计算型出题程序

此次程序是对上次程序的再次开发&#xff0c;我将自己视作另一个在开发者&#xff0c;在对自己前面程序进行再次审视时&#xff0c;有了别样的感受&#xff0c;自己写的程序&#xff0c;一定要为别人(也有可能是自己)留一条活路。闲话不多说&#xff0c;进入大家最喜欢的**环节…

使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程(三)

前篇 使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程&#xff08;一&#xff09;使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程&#xff08;二&#xff09;原文第十三步&#xff0c;Express API路由 第一个路由是用来创建角色…

匿名方法,lambad表达式,匿名类

其实lambad表达式就是“函数”或者说是“方法”写法的一个进化&#xff0c;越来越简化而已&#xff0c;如数学方法里的f(X)。 匿名方法&#xff1a;顾名思义&#xff0c;匿名方法就是没有名称的方法&#xff0c;但是有定义参数。 匿名方法最明显的好处就是可以降低另写一个方法…

Python3.1-标准库之Numpy

这系列用来介绍Python的标准库的支持Numpy部分。资料来自http://wiki.scipy.org/Tentative_NumPy_Tutorial&#xff0c;页面有许多链接&#xff0c;这里是直接翻译&#xff0c;所以会无法链接。可以大致看完该博文&#xff0c;再去看英文版。 1、先决条件 想要运行numpy&#x…

黑马Android全套视频无加密完整版

课程描述&#xff1a;java基础到javaweb开发&#xff0c;从基础入门到实战。安卓基础到实战。实体班近5个月的课程实录&#xff01;&#xff01;&#xff01;课程表&#xff1a;xml&dom_sax_dom4j编程tomcat与web程序结构与Http协议与HttpUrlConnectionjava web之servletja…

强大的CSS3动画库animate.css

今天要给大家介绍一款强大的CSS3动画库animate.css&#xff0c;animate.css定义了大概50多种动画形式&#xff0c;包括淡入淡出&#xff0c;文字飞入、左右摇摆动画等等。使用animate.css也非常简单&#xff0c;你可以给页面上的任意元素&#xff0c;特别是文字添加各种神奇的动…

有关系统环境变量的设置问题

不知道各位朋友有没有想过这样用的问题&#xff0c;我们在使用windows使用在cmd下运行Ping、Netstat等之类的命令时&#xff0c;为什么可以使用呢&#xff1f; 这些后面牵涉到哪些东西呢&#xff1f; 我们今天就来介绍下有关这方面的知识。我们现在以win8作为测试对象&#xff…

linux下安装配置jdk(解压版)

在linux下登录oracle官网&#xff0c;下载解压版jdk 传送门系统默认下载到“下载”目录中创建要将该文件解压的文件夹&#xff1a;其中 -p 参数代表递归创建文件夹&#xff08;可以创建多级目录&#xff09; 进入到下载目录&#xff0c;将下载好的jdk解压到指定目录配置环境…

谈谈Angular关于$watch,$apply 以及 $digest的工作原理

这篇文章主要是面向那些刚开始学AngularJs和想要了解数据绑定&#xff08;data-binding&#xff09;是怎么工作的&#xff0c; 如果你已经熟悉如何使用angularjs了&#xff0c;我强烈建议你不用阅读了。 angularjs使用者想要知道data-binding是如何工作的&#xff0c;就会遇到很…

Tachyon更名为 Alluxio,并发布1.0版本

详细参考 http://www.alluxio.org/releases/alluxio-1-0-0-release.html http://geek.csdn.net/news/detail/57243 http://www.alluxio.org/ Alluxio介绍 Alluxio 1.0版本&#xff0c;作为世界上首款以内存为中心的虚拟分布式存储系统&#xff0c;它能够统一数据访问并成为连接…

一幅长文细学MongoDB(四)——索引

4 索引 文章目录4 索引4.1 概述4.2 索引类型4.3 索引创建4.3 删除索引4.4 查看索引执行计划4.5 涵盖的查询4.1 概述 说明&#xff1a;索引支持在MongoDB中高效地查询。如果没有索引&#xff0c;MongoDB必须执行全集合扫描&#xff0c;即扫描集合中的每个文档&#xff0c;以选择…

【转】Android图片加载神器之Fresco-加载图片基础[详细图解Fresco的使用]

Fresco简单的使用—SimpleDraweeView 百学须先立志—学前须知&#xff1a; 在我们平时加载图片(不管是下载还是加载本地图片…..)的时候&#xff0c;我们经常会遇到这样一个需求&#xff0c;那就是当图片正在加载时应该呈现正在加载时的图像&#xff0c;当图片加载失败时应该呈…

对象映射工具AutoMapper介绍

AutoMapper是用来解决对象之间映射转换的类库。对于我们开发人员来说&#xff0c;写对象之间互相转换的代码是一件极其浪费生命的事情&#xff0c;AutoMapper能够帮助我们节省不少时间。 一. AutoMapper解决了什么问题? 要问AutoMapper解决了什么问题&#xff1f; 难道不是对象…

MindSpore安装教程【简洁易懂】

1 官网 MindSpore官网&#xff1a;MindSpore安装指南 2 关注社区 3 下载 查看自己python版本&#xff1a;使用python -V查看自己python版本 进入官网选择相应配置&#xff1a; 验证是否安装成功&#xff1a;python -c "import mindspore;mindspore.run_check()"&a…

一幅长文细学Vue(十三)——组合式中的生命周期

13 组合式API&#xff08;四&#xff09; 摘要&#xff1a;每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实例到 DOM&#xff0c;以及在数据改变时更新 DOM。在此过程中&#xff0c;它也会运行被…

OSGI 生命周期

1 生命周期管理 对于非模块化应用&#xff0c;生命周期将应用作为一个整体来操作&#xff1b;而对于模块化应用&#xff0c;则可以以细粒度的方式来管理应用的某一个独立部分。OSGi生命周期管理 OSGi生命周期层有两种不同的作用&#xff1a; 在应用程序外部&#xff0c;定义了对…

tomcat+nginx+redis实现均衡负载、session共享

在项目运营时&#xff0c;我们都会遇到一个问题&#xff0c;项目需要更新时&#xff0c;我们可能需先暂时关闭下服务器来更新。但这可能会出现一些状况:1.用户还在操作&#xff0c;被强迫终止了(我们可以看日志等没人操作的时候更新&#xff0c;但总可能会有万一)2.不知道的用户…

洛谷 P3184 [USACO16DEC]Counting Haybales数草垛

洛谷 P3184 [USACO16DEC]Counting Haybales数草垛 题目描述 Farmer John has just arranged his NN haybales (1 \leq N \leq 100,0001≤N≤100,000 ) at various points along the one-dimensional road running across his farm. To make sure they are spaced out appropria…

Entity Framework 6 Recipes 2nd Edition(13-2)译 - 用实体键获取一个单独的实体

问题 不管你用DBFirst,ModelFirst或是CodeFirst的方式,你想用实体键获取一个单独的实体.在本例中,我们用CodeFirst的方式. 解决方案 假设你有一个模型表示一个Painting(绘画)类型的实体,如Figure 13-2所示: Figure 13-2. The Painting entity type in our model 在代码In Listi…