前端事件系统(二)
本章将把重点放在于对于事件的委托机制,以及jquery的事件绑定方法做一些解析。本章并没有什么比较难懂的地方,也还没有深入到jQuery的事件系统内部。
事件委托
上一章讲了前端事件系统以及简单地对各个浏览器进行兼容的方法。对于要求不高的页面来说,之前的简单事件注册,就可以很好的胜任各种各样的工作了。但是,试想一种情况。倘若一个页面有着极大量的事件绑定的需求,那么我们之前的事件系统,就不得不一个个的去绑定事件。这样对性能来说,肯定是一种灾难,同时,动态增加的节点无法完成绑定的工作。因此,我们需要引入事件委托这一机制。
冒泡与捕获
我们知道dom的事件处理,分为捕获阶段,目标阶段,以及冒泡阶段三个部分。那么简单地讲一下这三个阶段吧,当我们点击了一个a标签的时候,整个事件的流程大致是下面的阶段
首先事件从dom树向下传送,逐个访问目标节点的祖先节点,同时将会对该事件的捕获事件监听器来进行检测,并执行,直到访问到目标节点,这一阶段就是我们说的捕获阶段;到达目标节点(即a标签)后,就会执行该事件监听器,这一阶段也就是目标节点;最后事件将会从目标节点开始,从dom树往上传送,再依次访问目标节点的祖先节点,并且执行对应的非捕获事件的事件监听器,并且执行
委托的原理
既然有了冒泡和捕获的概念,那么事件委托的原理也很好理解了。事件委托利用了事件可以传播的这一特性,并不使用事件本身对于事件来进行处理,而是将对事件的处理任务交予了其祖先节点来进行处理。
这样做,减少了对于页面上的dom操作。比如你对ul下的li绑定事件,通过事件委托,你只需要对于ul绑定一次事件,然后使用事件传播的这一特性,li的事件来进行工作,这样,极大地减少了绑定量,而且即使是动态增加的li节点,也同样是可以去执行该事件的。
jQuery的事件绑定
那么终于来到了jQuery的部分。从这里开始也将对jQuery的事件部分,尽个人所能来做一个解析,之后出现jQuery的源码的版本为2.1.4,解析过程中有不对的地方,欢迎打脸。
jQuery的事件绑定,有以下几种方法:
- 直接用click,blur等事件名的方法
- bind方法
- delegate
- live方法
- one方法
- on方法
那么我们对上面几种方法来一一分析(本章暂不会对作为核心的on的源码来进行分析)
-
直接用事件名进行事件绑定
即是直接采用类似.click(),.blur()的方式进行绑定
那么jQuery中是如何做到的呢
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { return arguments.length > 0 ? this.on( name, null, data, fn ) : this.trigger( name ); }; });
jQuery的实现很简单,将事件直接通过each方法加入jQuery.fn上,然后在内部根据参数的不同直接对于jQuery的on或者trigger方法进行调用。
-
bind方法
bind方法用于为一个元素绑定一个事件处理程序
先来看看jQuery中的实现吧
bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }
-
live方法
live方法拿到前面来说。这是jQuery现在已经不支持的方法。对于1.4.3以上的版本,推荐采delgate方法来,而对于1.7以上的版本,则推荐采用on方法来进行替代。具体采用live方法有什么弊端,jQuery的文档之中已经阐述的非常明白了,至于为什么会出现这个问题,本着刻苦求知的精神……我还并没有去看,附上翻译后的文档吧
因为更高版本的jQuery提供了更好的方法,没有.live()方法的缺点,所以.live()方法不再推荐使用。特别是,使用.live()出现的以下问题:
- 在调用 .live() 方法之前,jQuery 会先获取与指定的选择器匹配的元素,这一点对于大型文档来说是很花费时间的。
- 不支持链式写法。例如,$("a").find(".offsite, .external").live( ... ); 这样的写法是不合法的,并不能像期待的那样起作用。
- 由于所有的 .live() 事件被添加到 document 元素上,所以在事件被处理之前,可能会通过最长最慢的那条路径之后才能被触发。
- 在移动 iOS (iPhone, iPad 和 iPod Touch) 上,对于大多数元素而言,click 事件不会冒泡到文档 body 上,并且如果不满足如下情况之一,就不能和 .live() 方法一起使用:
- 使用原生的可被点击的元素,例如, a 或 button,因为这两个元素可以冒泡到 document。
- 在 document.body 内的元素使用 .on() 或 .delegate() 进行绑定,因为移动 iOS 只有在 body 内才能进行冒泡。
- 需要 click 冒泡到元素上才能应用的 CSS 样式 cursor:pointer (或者是父元素包含 document.documentElement)。但是依需注意的是,这样会禁止元素上的复制/粘贴功能,并且当点击元素时,会导致该元素被高亮显示。 - 在事件处理中调用 event.stopPropagation() 来阻止事件处理被添加到 document 之后的节点中,是效率很低的。因为事件已经被传播到 document 上。
- .live() 方法与其它事件方法的相互影响是会令人感到惊讶的。例如,$(document).unbind("click") 会移除所有通过 .live() 添加的 click 事件!
-
delegate方法
delegate方法也就是我们之前所提到的事件委托了。因为基于live之前的很多问题,jQuery在live之后的版本中增加了delgate方法(如今已经被on所取代)
来看看现在的delegate是如何实现的吧
delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }
-
one方法
为元素添加事件。并且在元素上的事件只可以执行一次
具体实现
one: function( types, selector, data, fn ) { return this.on( types, selector, data, fn, 1 ); }
-
on方法
on方法在这里不打算对源码进行阅读,因为可以看到,上面的所有方法都是对于on方法的调用。因此jQuery的事件系统,核心就在于on方法了。因此jQuery的on方法将在后面进行分析,而这里,只是对于on接口本身的一些说明。
on方法提供了绑定事件所有的功能。其API是这样的
.on( events [, selector ] [, data ], handler(eventObject) )
其实最后还有一个one参数,倘若传入了1,那么这个事件也就只执行一次,而实现的原理其实也就是使用off来解绑事件进行的操作了。
然后来看下别的参数分别是做些什么的吧
events: 类型: String 一个或多个空格分隔的事件类型和可选的命名空间,或仅仅是命名空间,比如"click", "keydown.myPlugin", 或者 ".myPlugin"。 selector: 类型: String 一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素。如果选择器是 null 或者忽略了该选择器,那么被选中的元素总是能触发事件。 data: 类型: Anything 当一个事件被触发时,要传递给事件处理函数的event.data。 handler(eventObject) 类型: Function() 事件被触发时,执行的函数。若该函数只是要执行return false的话,那么该参数位置可以直接简写成 false。
因此再来对于前面五种方法来进行统一的分析。
直接用事件名进行事件绑定的方法与bind的方法,直接对于selector参数赋值为了null,因此,对于采用这两种方法进行的事件绑定,其实本身是没有使用事件委托的,即是说并没有冒泡的过程,因此,采用这种方法的绑定对性能会有一定的损耗。
而delegate方法,传入了selector参数,因此,相当于也就是直接用on来实现的事件委托。
one方法之前也讲了,所以不再多说。
总结一下这章所讲的内容吧。本章对于事件绑定的几种方法(除on外),都进行了简单地讲解。与其说是对于jQuery源码的分析,倒不如说其实就是个简单地,对于jQuery使用的建议吧。jQuery绑定的核心还是落在on方法上,同时,jQuery的事件系统的思想可以说是极为经典的。在下一章,我们将对jQuery的事件系统进行更深入一些的分析(在我捉急的水平范围之内)。第三章估计要难产了,我一定会努力做完这个系列的,也算是对个人前端学习的一点鞭策吧。