jQuery-1.9.1源码分析系列(十)事件系统之事件体系结构

又是一个重磅功能点。

  在分析源码之前分析一下体系结构,有助于源码理解。实际上在jQuery出现之前,Dean Edwards的跨浏览器AddEvent()设计做的已经比较优秀了;而且jQuery事件系统的设计思想也是基于该思想的,所以我们先分析一下Dean Edwards前辈的事件绑定。

a. jQuery事件原型——Dean Edwards的跨浏览器AddEvent()设计

  源码解读

//事件添加方法
function addEvent(element, type, handler) {
  //保证每个不同的事件响应函数只有唯一一个id
 if (!handler.$$guid) handler.$$guid = addEvent.guid++;
  // 给element维护一个events属性,初始化为一个空对象。
 // element.events的结构类似于 { "click": {...}, "dbclick": {...}, "change": {...} }
 if (!element.events) element.events = {};
  // 试图取出element.events中当前事件类型type对应的对象(这个对象更像数组),赋值给handlers
  //如果element.events中没有当前事件类型type对应的对象则初始化
 var handlers = element.events[type];
  if (!handlers) {
     handlers = element.events[type] = {};
     // 如果这个element已经有了一个对应的事件的响应方法,例如已经有了onclick方法
  // 就把element的onclick方法赋值给handlers的0元素,此时handlers的结构就是:
  // { 0: function(e){...} },这也是为什么addEvent.guid初始化为1的原因,预留看为0的空间;
  // 此时element.events的结构就是: { "click": { 0: function(e){...} }, /*省略其他事件类型*/ }
  if (element["on" + type]) {
   handlers[0] = element["on" + type];
  }
 }
  // 把当前的事件handler存放到handlers中,handler.$$guid = addEvent.guid++; addEvent.guid = 1; 肯定是从1开始累加的
  //因此,这是handlers的结构可能就是 { 0: function(e){...}, 1: function(){}, 2: function(){} 等等... }
 handlers[handler.$$guid] = handler;
  //下文定义了一个handleEvent(event)函数,将这个函数,绑定到element的type事件上作为事件入口。
  //说明:在element进行click时,将会触发handleEvent函数,handleEvent函数将会查找element.events,并调用相应的函数。可以把handleEvent称为“主监听函数”
 element["on" + type] = handleEvent;
};
//计数器
addEvent.guid = 1;
function removeEvent(element, type, handler) {
 // delete the event handler from the hash table
 if (element.events && element.events[type]) {
  delete element.events[type][handler.$$guid];
 }
};
function handleEvent(event) {
  //兼容ie
  event = event || window.event;
  //this是响应事件的节点,这个接点上有events属性(在addEvent中添加的)
  //获取节点对应事件响应函数列表
 var handlers = this.events[event.type];
 // 循环响应函数列表执行
  for (var i in handlers) {
   //保持正确的作用域,即this关键字
     this.$$handleEvent = handlers[i];
  this.$$handleEvent(event);
 }
};

重新梳理一下数据结构,使用一个例子

<input type="text" id="chua" onClick="f0();">
function f0(){...}
function f1(){...}
function f2(){...}
function f3(){...}
var dom = document.getElementById("chua");
addEvent(dom,"click",f1);
addEvent(dom,"change",f1);
addEvent(dom,"change",f2);
addEvent(dom,"click",f3);
addEvent(dom,"change",f3);

  经过addEvent()函数之后,当前的数据结构为:

element: {
  onclick: handleEvent(event), //click事件的主监听函数
  onchage: handleEvent(event),  //change事件的主监听函数
  events: {
    click:{//这是一个类数组
      0: f0, //element已有的事件
      1: f1,  //下标1实际上就是f1.$$guid
      3: f3 //下标3实际上就是f3.$$guid,需要注意的是每一个响应事件都有一个唯一的$$guid作为下标
      ...
    },
    change:{//这是一个类数组
      1: f1,
      2: f2,
      3: f3
    }
 }
}

  事件系统会根据调用addEvent的顺序给每个响应函数(也就是addEvent(element, type, handler)中的第三个参数handler)打上标记$$guid。源码

  //保证每个不同的事件响应函数只有唯一一个id
 if (!handler.$$guid) handler.$$guid = addEvent.guid++;

  最终三个响应函数的$$guid标记分别是

  f1.$$guid = 1
  f2.$$guid = 2
  f3.$$guid = 3

  而根据源码中

handlers[handler.$$guid] = handler;

  那么某一个函数在任何事件响应函数集合中的下标位置是固定的。比如click和change事件都调用f3作为响应事件,那么f3在element.events.click以及element.events.change中的下标位置都是f3.$$guid = 3;即element.events.click[3] = element.events.change[3] = f3。

  这个时候假设又新添了一个事件绑定:addEvent(dom,"focus",f3);那么element.events.focus[3] = f3;这也是对象相比于数组的方便之处,数组不可能没有下标0,1,2就直接有3了,但是对象却可以,此时3是作为对象的一个属性名称。

  这样的设计,其实已经具备了jquery事件系统的雏形,包含了几个最主要的特点:

  1)element上的所有事件,将保存到element.events属性中,不是直接绑定到element上;这样一个事件可以有无数个响应函数。

  2)handleEvent作为element所有事件的“主监听函数”,有它统一管理element上的所有函数。

  3)所有浏览器都支持element["on" + type]事件绑定方式,跨浏览器兼容。

  好啦,明白了addEvent的事件结构,这个想法确实让人觉得眼前一亮。下面分析jQuery的事件结构

b. jQuery的事件结构

  所有的函数添加事件都会进入jQuery.event.add函数。该函数有两个主要功能:添加事件、附加很多事件相关信息。我们直接上源码,源码思想和Dean Edwards的跨浏览器兼容事件添加处理类似。

  源码分析

add: function( elem, types, handler, data, selector ) {
 var tmp, events, t, handleObjIn,
  special, eventHandle, handleObj,
  handlers, type, namespaces, origType,
  //获取elem节点对应的缓存数据
  elemData = jQuery._data( elem );
 //没有数据或文本/注释节点不能附加事件(但是允许附加普通对象)
 if ( !elemData ) {
  return;
 }
 //调用者能通过自定义数据替换handler
 if ( handler.handler ) {
  handleObjIn = handler;
  handler = handleObjIn.handler;
  selector = handleObjIn.selector;
 }
 //确保handler函数有唯一的ID,后续会用来查找/删除这个handler函数
 if ( !handler.guid ) {
  handler.guid = jQuery.guid++;
 }
 //如果是初次进入,初始化元素的事件结构和主事件响应入口
 if ( !(events = elemData.events) ) {
  events = elemData.events = {};
 }
 if ( !(eventHandle = elemData.handle) ) {
  eventHandle = elemData.handle = function( e ) {
   //当一个事件被调用后页面已经卸载,则放弃jQuery.event.trigger()的第二个事件,
   return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
   jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
   undefined;
  };
  //将elem作为handle函数的一个特征防止ie非本地事件引起的内存泄露
  eventHandle.elem = elem;
 }
 //多个事件使用空格隔开的处理
  //如jQuery(...).bind("mouseover mouseout", fn);
  //core_rnotwhite = /\S+/g;匹配空白字符
  types = ( types || "" ).match( core_rnotwhite ) || [""];
  t = types.length;
  while ( t-- ) {
  //rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
  //获取命名空间和原型事件
  tmp = rtypenamespace.exec( types[t] ) || [];
  type = origType = tmp[1];
  namespaces = ( tmp[2] || "" ).split( "." ).sort();
  //如果事件改变其类型,使用special事件处理器来处理更改后的事件类型
  special = jQuery.event.special[ type ] || {};
  //如果选择器已定义,确定special事件API类型,否则给他一个类型
  type = ( selector ? special.delegateType : special.bindType ) || type;
  //基于新设置的类型更新special
  special = jQuery.event.special[ type ] || {};
  // handleObj贯穿整个事件处理
  handleObj = jQuery.extend({
   type: type,
   origType: origType,
   data: data,
   handler: handler,
   guid: handler.guid,
   selector: selector,
   // For use in libraries implementing .is(). We use this for POS matching in `select`
   //"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
   //whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
   //用来判断亲密关系
   needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
   namespace: namespaces.join(".")
  }, handleObjIn );
  //初次使用时初始化事件处理器队列
  if ( !(handlers = events[ type ]) ) {
   handlers = events[ type ] = [];
   handlers.delegateCount = 0;
   //非自定义事件,如果special事件处理器返回false,则只能使用addEventListener/attachEvent
   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;
   }
  }
  //将事件对象handleObj添加到元素的处理列表,代理计数递增
  if ( selector ) {
   handlers.splice( handlers.delegateCount++, 0, handleObj );
  } else {
   handlers.push( handleObj );
  }
  //跟踪那个事件曾经被使用过,用于事件优化
  jQuery.event.global[ type ] = true;
 }
 //防止ie内存泄漏
 elem = null;
}

  依然用实例来说明jQuery的事件结构

<div id="#center"></div>
<script>
 function dohander(){console.log("dohander")};
 function dot(){console.log("dot");}
 $(document).on("click",'#center',dohander)
 .on("click",'#center',dot)
 .on("click",dot);
</script>

  经过添加处理环节,事件添加到了元素上,而且节点对应的缓存数据也添加了相应的数据。结构如下

elemData = jQuery._data( elem );
elemData = {
  events: {
    click: {//Array[3]
      0: {
        data: undefined/{...},
        guid: 2, //处理函数的id
        handler: function dohander(){…},
        namespace: "",
        needsContext: false,
        origType: "click",
        selector: "#center",//选择器,用来区分不同事件源
        type: "click"
      }
      1: {
        data: undefined/{...},
        guid: 3,
        handler: function dot(){…},
        namespace: "",
        needsContext: false,
        origType: "click",
        selector: "#center",
        type: "click"
      }
      2: {
        data: undefined,
        guid: 3,
        handler: function dot(){…},
        namespace: "",
        needsContext: false,
        origType: "click",
        selector: undefined,
        type: "click"
      }
      delegateCount: 2,//委托事件数量,有selector的才是委托事件
      length: 3
    }
  }
  handle: function ( e ) {…}/*事件处理主入口*/{
    elem: document//属于handle对象的特征
  }
}

  jQuery的处理和Dean Edwards的跨浏览器兼容事件添加处理类似,比如为每一个函数添加guid;使用events对象存放响应事件列表,有一个总的事件处理入口handle等。

  jQuery做了哪些改进?

  1)事件数据不再直接保存在节点上,而是使用jQuery缓存系统内(内部使用的缓存jQuery._data方式存取)

  2)事件委托:绑定到当前节点(例子中当前节点是document根节点)的处理函数不仅仅包含当前节点触发事件(click)响应时处理的事件(例子中selector为undefined时对应的处理函数dot);还代理了其他节点(例子中的#center节点)触发事件(click)响应时处理的事件(例子中selector为"#center"对应的处理事件doHandler和dot);委托机制在后续分析。

  3)增加了很多功能数据,比如命名空间namespace:这个主要用在自定义事件自定义触发,比如$(document).on("chua.click",'#center',dot),主动触发$("#center").trigger("chua.click")。还有额外数据data:虽然没有看到那个地方有被用到。

  到此jQuery的事件结构就清楚了。后面再分析事件的绑定和触发以及委托原理。

(0)

相关推荐

  • javascript之AJAX框架使用说明

    ajax编码落地算是团队框架建设的一个里程碑,使得框架拥有了一个可应用调试的版本.在ajax框架编码中主要想到了这么几点: 框架兼容性 方法全面而要简单快捷 可扩展性好 框架兼容性:ajax框架的兼容性主要表现在XMLHttpRquest对象生成上.根据不同类型浏览器或者不同库一般有三种方式 new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP"): new ActiveXObject("Msxml2.XMLHT

  • asp.net省市三级联动的DropDownList+Ajax的三种框架(aspnet/Jquery/ExtJs)示例

    本文主要列举了省市三级联动的DropDownList+Ajax的三种框架(aspnet/Jquery/ExtJs)示例.前段时间需要作一个的Web前端应用,需要用多个框架,一个典型的应用场景是省市三级联动,基于此应用,特将三种主要的ajax框架略作整理,方便有需要的朋友查阅. 在示例之前,我们先设置一个演示数据源,新建一个项目,项目结构如图: 主要文件如下:AreaModel.cs: 复制代码 代码如下: using System; using System.Collections.Generi

  • 基于JQuery框架的AJAX实例代码

    index.html 复制代码 代码如下: <html> <head> <title>jQuery Ajax 实例演示</title> </head> <script src="./js/jquery.js" type="text/javascript"></script> <script type="text/javascript"> $(docum

  • AJAX 验证框架13个

    一 ASP.NET AJAX Framework 1.ValidatorCallout 是ASP.NET AJAX Control Toolkit中的一员,老瓶装新酒,通过扩展的方式为ASP.NET的验证控件提供漂亮的弹出提示效果.缺点是:目前的版本还不能提供服务端Custom Validator的扩展. 同时,ValidatorCallout还支持自定义CSS样式,具体可参考官方页面上的"ValidatorCallout Theming" 一节,和这篇文章:http://www.im

  • jQuery-1.9.1源码分析系列(十)事件系统之事件包装

    在上篇文章给大家介绍了jQuery-1.9.1源码分析系列(十)事件系统之事件体系结构,本篇继续给大家介绍jquery1.9.1源码分析系列相关知识,具体内容请看下文吧. 首先需要明白,浏览器的原生事件是只读的,限制了jQuery对他的操作.举个简单的例子就能明白为什么jQuery非要构造一个新的事件对象. 在委托处理中,a节点委托b节点在a被click的时候执行fn函数.当事件冒泡到b节点,执行fn的时候上下文环境需要保证正确,是a节点执行了fn而非b节点.如何保证执行fn的上下文环境是a节点

  • 简单的前端js+ajax 购物车框架(入门篇)

    今天在公司实在没有事做,突然就想到写下商城的购物车的前端框架,当然我这里只有购物车的增删改查,也许写的并不是那么完善,但最重要的是一个入门,也希望js达人给些建议,好让我更上一个台阶. HOHO~~~开始咯: Js: 复制代码 代码如下: //为了省事,就没写自己的js ajax了 用了jquery的,当然你也可以添加到jquery的扩展方法内,哈哈,我太懒了,所以就写这里了. var _$ = { AJAX: function (urlparm, d, beforecall, successc

  • Jquery1.9.1源码分析系列(六)延时对象应用之jQuery.ready

    还记不记得jQuery初始化函数jQuery.fn.init中有这样是一个分支 //document ready简便写法$(function(){-}) } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } 所以$(fn)===$(document).ready(fn). 来看一下jQuery.fn.ready的源码 ready: function( fn ) { // Add t

  • jquery 框架使用教程 AJAX篇

    小试了下AJAX,感觉比prototype简洁多了,在JQuery中,AJAX有三种实现方式:$.ajax(),$.post,$.get(). XHTML(主要): <div id="result" style="background:orange;border:1px solid red;width:300px;height:400px;"></div> <form id="formtest" action=&qu

  • jQuery1.9.1源码分析系列(十六)ajax之ajax框架

    AJAX 简介 AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术. 您应当具备的基础知识 在继续学习之前,您需要对下面的知识有基本的了解: HTML / XHTML CSS JavaScript / DOM 如果您希望首先学习这些项目,请在我们的首页访问这些教程. 什么是 AJAX ? AJAX = 异步 JavaScript 和 XML. AJAX 是一种用于创建快速动态网页的技术. 通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重

  • jQuery1.9.1针对checkbox的调整方法(prop)

    在jquery 1.8.x中的版本,我们对于checkbox的选中与不选中操作如下: 判断是否选中 复制代码 代码如下: $('#checkbox').prop('checked') 设置选中与不选中状态: 复制代码 代码如下: $('#checkbox').attr('checked',true)$('#checkbox').attr('checked',false) 但此方法在jquery1.9.1中,有些处理不一样 IE10,Chrome,FF中,对于选中状态,第一次$('#checkbo

  • Jquery-1.9.1源码分析系列(十一)之DOM操作

    DOM操作包括append.prepend.before.after.replaceWith.appendTo.prependTo.insertBefore.insertAfter.replaceAll.其核心处理函数是domManip. DOM操作函数中后五种方法使用的依然是前面五种方法,源码 jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before&quo

  • Jquery AJAX 框架的使用方法

    刚开始学了 JQuqery, 众多的 $get(),...等等符号早已把我搞晕了.暂时就放弃了.后来学习 ASP.NET AJAX ,在微软的领导下,逐渐由服务器端转向客户端编程. 激起我客户端编程的兴趣,才想起学习一下了 Jquery.随着WEB2.0及ajax思想在互联网上的快速发展传播,陆续出现了一些优秀的Js框架,其中比较著名的有Prototype.YUI.jQuery.mootools.Bindows以及国内的JSVM框架等,通过将这些JS框架应用到我们的项目中能够使程序员从设计和书写

  • 零基础学习AJAX之AJAX框架

    上文(零基础学习AJAX之AJAX的简介和基础)对ajax异步请求服务器做了详细的介绍和基础应用,可以看出,ajax的一些过程是相对不变的.不必要每次发送请求都写一遍发送代码,一些ajax开发人员已经把他们的过程封装成ajax框架. 本节主要介绍ajaxLib和ajaxGold两款框架. 1.ajaxLib ajaxLib是一个非常小巧的ajax框架. 使用它首先将文件使用引入到页面中.改框架是一个直接获取XML的框架,调运函数如下: loadXMLDoc(url,callback,boolea

  • jQuery 1.9.1源码分析系列(十)事件系统之绑定事件

    事件绑定的方法有很多种,使用了jquery那么原理那种绑定方式(elem.click = function(){...}))就不太想推荐给大家了.最主要的原因是elem.click=fn这种方式只能绑定一个事件处理,多次绑定的只会保留最后一次绑定的结果. 下面给大家介绍jquery绑定事件的方式有哪些吧. 复制代码 代码如下: jQuery.fn.eventType([[data,] fn]) 比如eventType指的是事件类型,比如click: $("#chua").click(f

  • jQuery 1.9.1源码分析系列(十四)之常用jQuery工具

    为了给下一章分析动画处理做准备,先来看一下一些工具.其中队列工具在动画处理中被经常使用. jQuery.fn. queue(([ queueName ] [, newQueue ]) || ([ queueName ,] callback ))(获取或设置当前匹配元素上待执行的函数队列. 如果当前jQuery对象匹配多个元素:获取队列时,只获取第一个匹配元素上的队列:设置队列(替换队列.追加函数)时,则为每个匹配元素都分别进行设置.如果需要移除并执行队列中的第一个函数,请使用dequeue()函

  • jQuery 1.9.1源码分析系列(十三)之位置大小操作

    先给大家展示谢 jQuery.fn.css (propertyName [, value ]| object )(函数用于设置或返回当前jQuery对象所匹配的元素的css样式属性值.如果需要删除指定的css属性,请使用该函数将其值设为空字符串("") 注意:1.如果省略了value参数,则表示获取属性值:如果指定了该参数,则表示设置属性值.2.css()函数的所有"设置"操作针对的是当前jQuery对象所匹配的每一个元素:所有"读取"操作只针对

随机推荐