事件冒泡,事件捕获和事件委托

317 阅读8分钟

事件流
当浏览器发展到第四代时(IE4和Netscape Communicator 4),浏览器团队遇到一个很有意思的问题:页面的哪一部分会拥有特定的事件?想象下在一张纸上有一组同心圆,如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是一组圆。两家公司的开发团队在看待浏览器事件方面还是一致的。如果你单击了某个按钮,那么同时你也单击了按钮的容器元素,甚至整个页面。------引用自<JavaScript高级程序设计>

一.什么是事件流?

事件流描述的是从页面中接受事件的顺序。但有意思的是,IE和Netscape开发团队居然提出了两个截然相反的事件流概念。

  1. IE的事件流是 事件冒泡流

  2. 标准的浏览器事件流是 事件捕获流  
 "DOM2级事件"规定的事件流包括三个阶段:首先:事件捕获阶段、其次:处于目标阶段、最后:事件冒泡阶段  (事件捕获-->目标过程-->事件冒泡)

事件冒泡 : p->div->body->document->window

事件捕获 : window->document->body->div->p

二.事件冒泡

ie 的事件流叫事件冒泡,也就是说事件的传播为:从事件开始的具体元素,一级级往上传播到较为不具体的节点。如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件冒泡</title>
</head>
<body>
    <div>
        <p>点我</p>
    </div>
</body>
</html>

当我们点击P元素时,事件是这样传播的:

  1. p

  2. div

  3. body

  4. html

  5. document

  6. window

三.事件捕获

由Netscape提出的事件捕获原理恰巧与事件冒泡泡完全相反,事件捕获的思想是从不太具体的节点一级一级往下传播到具体的节点,还是这个的例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件捕获</title>
</head>
<body>
    <div>
        <p>点我</p>
    </div>
</body>
</html>

当我们点击p标签时时这样传递的

  1. window

  2. document

  3. html

  4. body

  5. div

  6. p

四.事件处理程序

事件 :用户或者浏览器自身执行的某种动作,如 click , load , mouseover
事件处理程序(事件监听器) :响应某个事件的函数 .如 onclick , onload , onmouseover

HTML事件处理程序

像这样使用

<input type="button" value="点我" onclick="alert('点的好')"/>

但同时这样使用会有很多问题,例如

  1. 时差问题:如果用户在HTML元素出现之初就触发了事件,但事件并不具备执行条件,那么就睡发生错误. 解决方法是将HTML事件处理程序封装在一个try-catch语句中
  2. 程序的作用域链在不同浏览器中会导致不同结果
  3. 耦合 HTML和JS代码耦合紧密 如果要更换事件处理程序,就要动两个地方

DOM0级事件处理程序

将一个函数赋值给一个事件处理程序属性

使用原因有两点:一简单方便. 二 有跨浏览器的优势 JavaScript指定事件处理程序首先需要取得一个要操作的对象,像这样使用

var btn = document.getDocumentById("myBtn")
btn.onclick = function() {
   alert( "this.id);//myBtn
};

删除DOM0级事件处理程序就是将属性的值设置为null

	btn.onclick = null

DOM2级事件处理程序

addEventListener(处理的事件名,事件处理程序函数,布尔值);
removeEventListener(处理的事件名,事件处理程序函数,布尔值);
布尔值:捕获阶段调用事件处理程序:true。冒泡阶段调用:false。默认为false

使用原因:可以添加多个事件处理程序 . 事件按照添加先后顺序执行。

var btn = document.getDocumentById("myBtn")
btn.addEventListener("click" , function() {
    alert(this.id);//myBtn
}, false);

最好写成

var handler = function(){
    alert(this.id);
};
btn.addEventListener(“click”, handler, false);//这样做的好处在于可以移除事件处理程序

删除DOM2级事件处理程序:通过addEventListener()添加的程序处理事件必须使用removeEventListener()来移除;;移除时传入的参数必须跟移入时的参数完全一致,这就一意味着通过ddEventListener()添加的匿名函数无法被移除.

五.阻止事件冒泡/捕获

取消默认操作

w3c 的方法是 event.preventDefault(), IE 则是使用 event.returnValue = false;

    这是阻止默认事件的方法,调用此方法是,事件不会被发生,但是会发生冒泡,冒泡会传递到上一层的父元素

阻止冒泡

w3c 的方法是 event.stopPropagation(),IE 则是使用 event.cancelBubble = true

    这是阻止事件的冒泡方法,不让事件向document上传递,但是默认事件任然会执行,当你调用这个方法的时候,如果点击一个事件,这个事件仍然会发生.

    调用 stopPropagation() 方法可以在事件传播期间的任何时间调用,它能工作在捕获阶段、事件目标本身中和冒泡阶段.

return false

    这个方法比较暴力,他会同时阻止事件冒泡也会阻止默认事件;写上此代码,事件不会被发生,事件也不会传递到上一层的父元素;可以理解为return false就等于同时调用了event.stopPropagation()和event.preventDefault()

六.内存与性能

    在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。 解决方法:事件委托

事件委托

那什么叫事件委托呢?它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。那这是什么意思呢?

试想一下,有三个舍友到中午吃饭的时候,恰巧他们又都很懒不愿意去食堂,这时作为舍长的你恰巧要去食堂吃饭,更恰巧的是对门宿舍的人看你去买饭也决定让你帮忙带饭,所以他们把买饭这个行为委托给了你,你在买完饭后在分发给每一个人,这就是事件委托,这里面有两层意思

  • 现在委托给你的舍友都是需要买饭的.即程序中的现有的dom节点是有事件的
  • 后来对象宿舍的人也决定让你帮忙带饭.即程序中新添加的dom节点也是有事件的

事件委托的原理

事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件

事件委托的实现

<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

下面是实现点击弹出123

window.onload = function(){
    var oUl = document.getElementById("ul1");
    var aLi = oUl.getElementsByTagName('li');
    for(var i=0;i<aLi.length;i++){
        aLi[i].onclick = function(){
            alert(123);
        }
    }
}

这是我们通常的做法,那一共进行了几次DOM操作呢?首先要找到ul,然后遍历li,然后点击li的时候,又要找一次目标的li的位置,才能执行最后的操作,每次点击都要找一次li

下面用事件委托来实现:

window.onload = function(){
    var oUl = document.getElementById("ul1");
   oUl.onclick = function(){
        alert(123);
    }
}

这里用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发,当然,这里当点击ul的时候,也是会触发的

我们还可以将事件代理的效果跟直接给节点的事件效果一样,比如说只有点击li才会触发,Event对象提供了一个属性叫target,可以返回事件的目标节点,我们称为事件源

window.onload = function(){
&emsp;&emsp;var oUl = document.getElementById("ul1");
&emsp;&emsp;oUl.onclick = function(ev){
&emsp;&emsp;&emsp;&emsp;var ev = ev || window.event;
&emsp;&emsp;&emsp;&emsp;var target = ev.target || ev.srcElement;//兼容IE
&emsp;&emsp;&emsp;&emsp;if(target.nodeName.toLowerCase() == 'li'){
&emsp; &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;	alert(123);
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;  alert(target.innerHTML);
&emsp;&emsp;&emsp;&emsp;}
&emsp;&emsp;}
}

总结

事件是将JavaScript与网页联系起来的主要方式

使用事件时要注意内存与性能问题

  1. 限制页面中事件处理程序的数量,数量太多会大量占用内存,导致卡顿等问题
  2. 建立再事件冒泡机制上的事件委托可以有效减少事件处理内存