背景知识
什么是事件?
直观的说就是网页上发生的事情,大部分是指用户的鼠标动作和键盘动作,如点击、移动鼠标、按下某个键。为什么说大部分呢,因为事件不单单只有这两部分,还有其他的例如document的load和unloaded。只不过我们更加关注的是用户的操作。
事件模型:规范事件的定义的一种标准
事件的三种模型:
- 原始事件模型(DOM 0级事件模型)
- IE事件模型
- DOM2事件模型
DOM2事件模型:
此模型是W3C制定的标准模型,既然是标准,那大家都得按这个来,我们现在使用的现代浏览器(指IE6~8除外的浏览器)都已经遵循这个规范。W3C制定的事件模型中,“DOM2级事件”中规定的事件流同时支持了事件捕获阶段和事件冒泡阶段,而作为开发者,我们可以选择事件处理函数在哪一个阶段被调用。
一次事件的发生包含三个过程:
(1)capturing phase:事件捕获阶段。
事件被从document一直向下传播到目标元素,在这过程中依次检查经过的节点是否注册了该事件的监听函数,若有则执行。
(2)target phase:事件处理阶段。
事件到达目标元素,执行目标元素的事件处理函数.
(3)bubbling phase:事件冒泡阶段。
事件从目标元素上升一直到达document,同样依次检查经过的节点是否注册了该事件的监听函数,有则执行。
所有的事件类型都会经历captruing phase(事件捕获),但是只有部分事件会经历bubbling phase(事件冒泡)阶段,例如submit事件就不会被冒泡。
1. 是什么?
<div id="outer">
<p id="inner">Click me!</p>
</div>
上面的代码当中一个div元素当中有一个p子元素,如果两个元素都有一个click的处理函数,那么我们怎么才能知道哪一个函数会首先被触发呢?
为了解决这个问题微软和网景提出了两种几乎完全相反的概念。
**事件流 : ** 指的是页面中接收事件的顺序.
事件冒泡
微软提出了名为事件冒泡(event bubbling)的事件流。
事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。
当一个DOM元素上的事件被触发的时候(如:按钮点击事件),这个元素的所有父元素 中,如果也绑定有该相同事件,则也会被触发, 触发的顺序就是先从 : 当前元素的事件 ==> 临近父元素 ==> 父元素......,这一过程被称为事件冒泡
因此上面的例子在事件冒泡的概念下发生click事件的顺序应该是:
p -> div -> body -> html -> document
IE,火狐和chrome浏览器都是事件冒泡.
事件捕获
网景提出另一种事件流名为事件捕获(event capturing)。
当一个DOM元素上的事件被触发的时候(如:按钮点击事件),这个元素的所有父元素 中,如果也绑定有该相同事件,则也会被触发, 触发的顺序就是先从 : ....... 父元素 ==> 临近父元素 ==> 当前元素的事件,这一过程被称为事件捕获
与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
上面的例子在事件捕获的概念下发生click事件的顺序应该是:
document -> html -> body -> div -> p
图解:
2. 解决了什么问题?
这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。
http://www.imooc.com/article/9833
3. 执行原理
<ul>
<li>
<p>
<a> </div>
</p>
</li>
</ul>
事件捕获阶段:事件从最上一级标签开始往下查找,直到捕获到事件目标(target)。
事件冒泡阶段:事件从事件目标(target)开始,往上冒泡直到页面的最上一级标签。
事件捕获当你使用事件捕获时,父级元素先触发,子级元素后触发,即div先触发,p后触发。
事件冒泡当你使用事件冒泡时,子级元素先触发,父级元素后触发,即p先触发,div后触发。
4. 如何使用?
说到事件的执行顺序,那么我们需要知道一个给元素添加事件的方法, 即给某元素动态绑定事件
W3C为我们提供了addEventListener()函数用来为指定的dom元素动态绑定事件。
语法:
element.addEventListener( event , function , useCapture )
提示: 使用 removeEventListener()方法来移除 addEventListener() 方法添加的事件句柄。
function sayHello() {
console.log("hello");
}
var myDiv = document.getElementById("myDiv");
myDiv.addEventListener("click", sayHello);
这样我们点击id为myDiv的元素时,控制台就会输出"Hello"。
事件冒泡
有如下html代码:
下面设置了四个函数用来进行事件绑定:
使用下面的代码,我们可以获取四个元素对应DOM
现在,我试着同时分别为grandpa和grandson绑定sleep和doingHomework事件:
这时我们点击最外层的grandpa时,当然会触发sleep函数,然而当我们点击grandson时,控制台的输出如下:
原因:这是因为grandson在grandpa之上,当点击grandson时,同时也在grandpa上进行了点击操作,所以在执行了doingHomework后,还会触发grandpa的sleep函数。
这种当满足条件后从子元素到父元素依次触发其上事件的处理方式叫做事件冒泡
我们也为father和child分别绑定watchTV和playingCard函数
事件冒泡()
grandpa.addEventListener("click", sleep);
grandson.addEventListener("click", doingHomework);
father.addEventListener("click", watchTV);
child.addEventListener("click", playingCard);
事件捕获
事件捕获与事件冒泡完全相反,先触发祖先元素的事件,然后再逐级触发子元素的事件。默认情况下,绑定事件时,采用事件冒泡原则,如果想要进行事件捕获的话,需要设置一个参数 。
可以为addEventListener函数添加第三个参数useCapture,参数值是布尔值,默认是false。当useCapture为false时,事件处理采取事件冒泡的原则,当userCapture为true时,则采取事件捕获的原则
这时,当点击grandson时,就会先执行祖先元素的事件,再执行后代元素的事件了,控制台的输出如下图所示:
虽然默认情况下,useCapture的值是false,但我推荐我们在绑定函数时把它明显的写出来以避免浏览器兼容性的问题。
事件冒泡与事件捕获要是同时进行怎么办
有思想的同学肯定会思考这样一个问题,在上述绑定事件的代码中,第三个参数不是全部设置的true,就是全部设置成false,那如果既有true,又有false,有的元素设置成按事件冒泡处理,有的元素设置成按事件捕获处理,那怎么办呢?
直接告诉大家答案,我们的浏览器更“喜爱”事件捕获:
它会先把useCapture为false的元素绑定事件放到一边,按照事件捕获正常的顺序执行useCapture为true的元素绑定事件,最后在按照事件冒泡顺序执行useCapture为false。
现在我们作如下更改
按照上述原则,当点击grandson时,先执行useCapture为true的元素的绑定事件,又按照事件捕获原则,先执行grandpa的事件,再执行child的事件。之后,再按照事件捕获顺序执行useCapture为false的事件,输出结果如下:
阻止事件冒泡和捕获
我们可以利用时间对象event的stopPropagation()
方法阻止事件的进一步传播。
我们修改一下doingHomework函数:
发现事件执行到doingHomework就被阻断了,其后不会在事件传播到父元素。
值得注意的是,event.stopPropagation()函数并不会阻止其下函数内容的执行。
*如果你使用的是jquery的事件绑定,也可以直接在函数中使用return false来阻止事件的传播(当然event.stopPropagation同样有效),与event.stopPropagation不同的是,return false会阻止同函数下面的代码执行 *
传统绑定事件方式在一个支持W3C DOM的浏览器中,像这样一般的绑定事件方式,是采用的事件冒泡方式。
ele.onclick = doSomething2
IE浏览器
如上面所说,IE只支持事件冒泡,不支持事件捕获,它也不支持addEventListener函数,不会用第三个参数来表示是冒泡还是捕获,它提供了另一个函数attachEvent。
ele.attachEvent("onclick", doSomething2);
不是所有的事件都能冒泡,例如:blur、focus、load、unload。
事件冒泡的好处