首先,我们知道微软提出的是事件冒泡,网景提出的是事件捕获。这两个概念的提出是为了解决页面中事件流(即事件发生顺序)的问题。
<div id="outer">
<p id="inner">点击我!</p>
</div>
上面的代码中一个div元素当中有一个p子元素,如果两个元素都有一onclick事件处理函数,那么我们怎么才能知道哪一个函数会先被触发呢?
微软和网景这两个公司提出两种相反的概念解决了这个问题。
事件冒泡
事件冒泡可以形象地比喻为一颗石头投入水中,泡泡会一直从水底冒出水面,也就是说,事件会最内层的元素开始触发,一直往上传播,直到document对象为止。
所以上面的例子在事件冒泡的概念下发生click事件的顺序应该是p -> div -> body -> html -> document。
事件捕获
事件捕获是和事件冒泡完全相反的概念,也就是说,事件会从最外的document对象开始触发,然后不断往里触发。直到最里面的元素被触发为止。
上面的例子在事件捕获的概念下发生click事件的顺序应该是document -> html -> body -> div -> p。
在处理事件冒泡中,IE,DOM2级以及DOM0级分别给出以下的处理方法:
var EventUtil={
//通过addEventListener 添加监听事件
addHandler:function(element,type,handler){
//element:元素,type:事件类型“click”,handler:处理函数
if(element.addEventListener){ //Dom2级的处理事件
element.addEventListener(type,handler,false);
}else if(element.attachEvent){ // IE处理事件
element.attachEvent('on'+type,handler);
}else{ //Dom0级的处理时事件
element['on'+type]=handler;
}
},
//通过removeHandler
//删除事件
removeHandler:function(element,type,handler){
if(element.removeHandler){//Dom2级的处理事件
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){ // IE处理事件
element.detachEvent('on'+type,handler);
}else{//DOM0级处理事件
element['on'+type]=null;
}
}
}
addEventListener的第三个参数
“DOM2级事件”中规定的事件流同时支持了事件捕获阶段和事件冒泡阶段,而作为开发者,我们可以选择事件处理函数在哪一个阶段被调用。
addEventListener方法用来为一个特定的元素绑定一个事件处理函数,是JavaScript中的常用方法。addEventListener有三个参数:
element.addEventListener(event, function, useCapture)第一个参数是需要绑定的事件,第二个参数是触发事件后要执行的函数。而第三个参数默认值是false,表示在事件冒泡的阶段调用事件处理函数,如果参数为true,则表示在事件捕获阶段调用处理函数。
阻止事件的冒泡行为以及阻止冒泡函数( stopPropagation())
有时候我们开发网页的时候不希望页面出现事件冒泡,我们可以使用阻止事件冒泡处理函数。这个函数使用如下:
stopPropagation:function(evnt){
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=ture;
}
其中的event.cancelBubble=true是指取消事件冒泡,将事件停止下来,不让事件往上面的元素传递。
冒泡还是 捕获?
对于事件代理来说,在事件捕获或者事件冒泡阶段处理并没有明显的优劣之分,但是由于事件冒泡的事件流模型被所有主流的浏览器兼容,从兼容性角度来说还是建议大家使用事件冒泡模型。
IE浏览器兼容
IE浏览器对addEventListener兼容性并不算太好,只有IE9以上可以使用。
要兼容旧版本的IE浏览器,可以使用IE的attachEvent函数
object.setCapture();
object.attachEvent(event, function)
两个参数与addEventListener相似,分别是事件和处理函数,默认是事件冒泡阶段调用处理函数,要注意的是,写事件名时候要加上”on”前缀(”onload”、”onclick”等)。
基于事件冒泡的事件委托
什么事件委托呢?
红宝书上面讲的是:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。我上网查了一下大牛们举的例子都是一样的,接收快递的例子;
来点代码吧:
- 111
- 222
- 333
- 444
- 555
我们要给li添加一个点击事件,使得点击li的时候弹出“123”;我们通常挥这样去写代码
var oUl = document.querySelector("ul");
var oLi = oUl.querySelectorAll("li");
for(var i=0;i<oLi.length;i++){
oLi[i].onclick = function(){
alert("123");
}
}
我们会给li循环绑定点击事件。可是这样做会影响整个网页的运行性能,因为不断地需要与dom节点交互,访问dom的次数越多引起浏览器重绘和重排的次数就越多,就会影响整个页面的交互就绪时间。这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;
如果使用事件委托的话:就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;
每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了(内存不够用,是硬伤,哈哈)(容易造成了内存溢出),比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,那只能说呵呵了,如果用事件委托,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好。
如何实现事件委托
上面说到事件冒泡:事件执行顺序是从最内部的标签一直向上冒泡到document;拿上面的例子来说,li>ul>docment;有这样的一个机制,就是我们给ul添加click事件,那么点击li的时候可以冒泡到ul。,所以都会触发点击事件。
但是我们会发现一个问题:那就是我们无法确定点击那个li标签;怎么办呢?
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,当然,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,我们需要转成小写再做比较
这样改下就只有点击li会触发事件了,且每次只执行一次dom操作,如果li数量很多的话,将大大减少dom的操作,优化的性能可想而知!
总结:
那什么样的事件可以用事件委托,什么样的事件不可以用呢?
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。
