第二十三课:事件系统2

本课主要来讲解一下jQuery是如何实现它的事件系统的。

我们先来看一个问题:

如果有一个表格有100个tr元素,每个都要绑定mouseover/mouseout事件,改成事件代理的方式,可以节省99次绑定,更何况它还能监听将来添加的tr元素。这就是jQuery中的live方法。

这种机制使用的是事件冒泡机制实现的,我们把事件处理函数绑定在tr的父元素上,然后再tr上面触发的事件会冒泡到tr的父元素,因此父元素就可以触发这个事件处理函数,在事件处理函数中就可以通过这个event获取到事件源,然后对事件源tr进行处理。

不过,live方法需要对一些不冒泡的事件做一些处理,比如一些表单事件,有的只冒泡到form,有的冒泡到document,有的压根不冒泡。

对于focus,blur,change,submit,reset,select等不会冒泡的事件(有些浏览器支持,有些不支持),在标准浏览器下,我们可以设置addEventListener的最后一个参数为true(捕获)就行了,因为捕获操作的话,事件会从document到事件源,这时就能使用事件代理机制了。IE就比较麻烦了,要用focusin代替focus,focusout代替blur,selectstart代替select。change,submit,reset就复杂了,必须用其他事件来模拟,还要判断事件源的类型,selectedIndex,keyCode等相关属性。这个课题被一个叫reglib的库搞定了。jQuery就是吸取了reglib的经验,兼容了各种事件。使用live方法进行事件代理时,最好是绑定目标元素的父元素,因为绑定document的话,在IE下有时还是会失灵。

首先,来看一下jQuery.event.add的源码解读:

add = function(elem,types,handler,data,selector){

  var elemData,eventHandle,events,t,tns,type,namespaces,handleObj,handleObjIn,handlers,special;   //定义一系列的变量

  if(elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data(elem))){

  //如果元素是文本节点(IE下访问文本节点会抛错,因为事件源不能为文本节点),就直接返回。元素也不能是注释节点。事件类型和事件处理函数不能没有。如果元素不能添加自定义属性,也直接返回。如果元素elem可以添加自定义属性,会在jQuery的缓存系统中添加这个元素的缓存对象,并返回,这时elemData就是缓存系统中,以元素elem为属性的对象。

    return;

  }

  if(handler.handler){     //如果传进来的事件处理函数是一个json对象{handler:function(){处理函数},selector:执行上下文}

    handleObjIn = handler;

    handler = handleObjIn.handler;

    selector = handleObjIn.selector;

  }

  if(!handler.guid){    //如果事件处理函数没有唯一的guid属性,就赋值一个

    handler.guid = jQuery.guid ++;         //jQuery.guid从1开始,累加,因此每一个事件处理函数的guid属性都是唯一的。

  }

  events = elemData.events;    //如果此元素elem之前没有绑定过事件处理函数,它在缓存系统中以元素elem为属性的对象的events属性将是undefined

  if(!events){  //也就是说,如果之前给这个元素elem绑定过事件处理函数,那么这时的events将是一个对象,不会进入if语句

    elemData.events = events = {};   //设为对象

  }

  eventHandle = elemData.handle;   //第一次绑定时,为undefined

  if(!eventHandle){      //给此元素elem绑定事件处理函数 function,这个function会处理用户绑定该元素的所有事件处理函数(哪种类型的事件触发,就执行哪种事件绑定的所有事件处理函数)

    elemData.handle = eventHandle = function(e){

      ....

    }

    eventHandle.elem = elem;    //这个function的elem属性就是这个元素elem

  }

  types = jQuery.trim(hoverHack(types)).split(" ");  //因为绑定事件类型时,可能传入多个事件,比如:"mouseover mouseout",需要转换成[mouseover,mouseout],hoverHack是来处理hover这个事件的,它需要转换成mouseenter和mouseleave两个事件。

  for(t=0;t<types.length;t++){

    ....

    type = types[t];

    special = jQuery.event.special[type] || {}; //并不是所有的事件都能直接使用,比如:火狐下没有mousewheel,需要用DOMMouseScroll冒充

    type = (selector ? special.delegateType: special.bindType) || type; //有些事件只需要在事件代理时,需要冒充。比如:focus,blur,不冒泡的,事件代理使用的就是冒泡机制,所有需要做特殊化处理。

    special = jQuery.event.special[type] || {};

    handleObj = jQuery.extend({

      type:type,   //处理后的事件类型

      origType:types[t],  //真正的事件类型

      data:data,

      handler:handler,

      guid:handler.guid,

      selector:selector

      .....

    }, handleObjIn)

    handlers = events[type];   //查看此元素在缓存系统中是否有此事件类型的数组处理函数。第一次绑定此type类型的事件时,是undefined。

    if(!handlers){

      handlers = events[type] = [];  //此数组就是用来装载此类事件的事件处理函数的

      handlers.delegateCount = 0; //记录要处理的事件代理回调函数的个数

      .....

      if(elem.addEventListener){

        elem.addEventListener(type,eventHandle,false);//给元素绑定此类型事件的事件处理函数,如果下次继续给此元素绑定此类型事件的事件处理函数,就不会调用这里,直接把事件处理函数放进events[type]数组。

      }else{

        elem.attachEvent("on"+type,eventHandle);     //eventHandle事件处理函数就是elemData.handle方法,就是上面定义的function,里面会操作所有的事件处理函数

      }

    }

    ......

    if(selector) {  //如果是使用事件代理,那么就把事件描述对象放到数组的前面

      handlers.splice(handlers.deletegateCount, 0 , handleObj);

    }else{

      handlers.push(handleObj);

    }

    ....

  }

  elem = null;   //防止ie内存泄露

}

add方法的目的是,将用户传递的所有参数,合成一个handleObj对象,并把这个对象放到缓存系统中。放入缓存系统时,需要遵守一定的规则,必须是elem元素在缓存系统中对应的位置,同时,针对不同的事件类型type,创建不同的事件处理函数数组(数组中的每一项就是一个事件描述对象),每个事件处理函数数组,处理相对应的事件。因此对于同一个元素,并且同一事件,它只会绑定一次,如果对元素div1绑定两次click,那么第二个的事件处理函数,将直接添加到事件处理函数数组中(div1.click = [],其中的div1不是元素本身,而是缓存系统中跟元素div1相对应的唯一的(UUID)属性对象)。

同时add方法,会给元素elem的types中的事件类型绑定一个统一的事件处理函数eventHandle,比如:给元素elem绑定click和mouseover,它们的事件处理函数都是eventHandle。只是在这个方法中,会根据事件类型的不同,触发响应的事件处理函数。比如,触发click事件,eventHandle只会执行div1.click数组中的事件处理函数,而不是执行div1.mouseover数组中的事件处理函数。

从上可知,jQuery的回调不再与元素直接挂钩,而是通过UUID访问数据缓存系统,再根据事件类型得到一组事件描述对象。

元素与数据缓存系统之间的结构图:

elem在缓存系统中唯一的对应值是elemData.它有两个属性值handle和events,handle是一个回调函数,并且它有一个elem属性指向elem元素。events是一个json对象,它里面有很多属性,每个属性都是事件的类型,比如,click,mousemove。click这种属性值是一个数组,数组中存放的是事件描述对象。同时这个数组还有一个delegateCount属性,它代表代理事件描述对象的个数。事件描述对象是一个json对象,里面有各种属性,其中handler是真正的事件处理函数。

加油!

时间: 2024-10-26 00:59:03

第二十三课:事件系统2的相关文章

第二十三课

第二十三课第一单元语法部分 Vておく<提前>:预先…… 口语形式:-とく 说明:   A.表示为后面要做的事情事先做好某种准备. B.表示采取某种行为,并使其结果的状态持续下去.   C.有时表示一种临时的措施. 例句:     1 日本へ行く前に日本語を習っておくつもりだ.    2 電気は消さないで 練習: 1.事先打个电话问一.朝までつけておこう.    3 その場で一応の手当てをしておいて.病院へ連れていった.下比较好 2.预先磨好刀. Vてある<客体存续的状态>: 说明:

NeHe OpenGL教程 第二十三课:球面映射

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第二十三课:球面映射 球面映射: 这一个将教会你如何把环境纹理包裹在你的3D模型上,让它看起来象反射了周围的场景一样. 球体环境映射是一个创建快速金属反射效果的方法,但它并不像真实世界里那么精确!我们从18课的代码开始来创建这个教程

JAVA学习第二十三课(多线程(二))- (多线程的创建方式二 :实现Runnable接口(常用))

当一个类有父亲,但是其中的功能还希望实现线程,那么就不能采用继承Thread的方式创建线程 那么就可以通过接口的方式完成 准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行 实现Runnable接口,Runnable接口中只有一个方法run 一.创建线程的第二种方法 Runnable的出现仅仅是将线程的任务进行了对象的封装 /* * 创建线程的第二种方法 * 1.定义类实现Runnable接口 * 2.覆盖接口中的fun方法,将线程的任务代码封装到run方法中 * 3.通过Thread

【批处理学习笔记】第二十三课:用户变量和变量引用

用户变量    编写批处理程序时,用户根据需要自己定义的变量称之为用户变量.用户变量类似于C语言里面的变量,仅仅在定义该变量的程序中有效.    用户变量由set命令定义,这是批处理中非常非常重要的一个操作,从而使set命令成为批处理里面使用频率最高的几个命令之一.关于set命令的使用,参考set /?,本教程也会在后面对其进行讲解. 变量引用    前面的几节课里面,我们已经看到了如何引用变量,即直接用变量名操作变量,通过"%"或"!"来获取变量的值.其中,只有在

Spring入门第二十三课

我们看基于XML配置的方式配置AOP 看代码: package logan.study.aop.impl; public interface ArithmeticCalculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); } package logan.study.aop.impl; public class ArithmeticCalculato

22.2015.08.18第二十三课mvc1,2(mvc环境搭建)

(1)修改Web.config的连接符,将数据库信息正确填写. <connectionStrings> <add name="con" connectionString="Database=Tjfx;Server=192.168.200.16;Integrated Security=false;Uid=sa;Password=123;" providerName="System.Data.SqlClient"/> <

linux学习笔记-第二十三课-LNMP-Nginx与PHP配置(二)

一.Nginx 1.Nginx全局配置 [[email protected] ~]# vim /usr/local/nginx/conf/nginx.conf user nobody nobody; worker_processes 2; error_log /usr/local/nginx/logs/nginx_error.log crit; pid /usr/local/nginx/logs/nginx.pid; worker_rlimit_nofile 51200; events {   

ParisGabriel:Python全栈工程师(0基础到精通)教程 第二十三课(每周总结:2)

ParisGabriel 每天坚持 一天一篇 点个订阅吧  灰常感谢    当个死粉也阔以 week summer: Python人工智能从入门到精通 函数式编程: 是指用一系列函数解决问题 每一个函数完成细小的功能,一系列函数的任意组合可以完成 大问题 函数仅接受输入并产生输入,不包含任何影响输出的内部 状态函数的可重用性: 如果一个函数的输入参数一定,则返回结果必须一定的函数 称为可重用函数 可重入和不可重入就是访问除局部变量以外的变量函数式编程要求: def 创建函数最好不要访问局部作用域

第二十三课 顺序表和单链表的对比分析

问题: 如何判断某个数据元素是否存在于线性表中? 查找一个元素是否在线性表中,每次查找就需要使用for循环,因此,我们需要封装一个find成员函数. 在List.h中添加find函数: SeqList.h中添加find的实现: LinkList.h中添加find的实现: 测试程序如下: 1 #include <iostream> 2 #include "LinkList.h" 3 4 5 using namespace std; 6 using namespace DTLi