当浏览器开发到第四代的时候,浏览器的开发团队遇到了一个很有意思的问题:页面的哪一部分会拥有特定的事件?要明白这个问题问的是什么,我们可以想下面的一个问题,加入我们在一张纸上画了一组同心圆.如果把手放在纸上,那么手指指向的不是那一个特定的圆而是纸上所有的圆.两家浏览器开发团队在看待浏览器事件方面还是一致的.如果我们单击 某个按钮,他们都认为单击事件并不仅仅发上在按钮身上.换句话说,你也同时单击了按钮的容器元素,甚至也单击了整个页面.
事件流描述的就是从页面中接收事件的顺序.但是IE
和 Netspace
开发团队提出了差不多但是完全相反的事件流的概念.
IE
的事件流是事件冒泡流Netspace
的事件流是事件捕获流IE
的事件流叫做事件冒泡流,即事件开始由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到不太具体的节点(文档)
事件默认是冒泡的,子元素触发事件时,会沿着DOM结构一层一层向上传递,这个过程称为事件冒泡。
注意:子元素被定位到了其他位置(视觉上脱离了父元素),也会见事件一层一层向上传递
看下面的例子:
在这里绑定事件用的是 DOM2级事件
,它接收三个参数:
<style>
* {
margin:0;
padding:0;
}
#orange{
position: relative;
width: 300px;
height: 300px;
margin:100px auto;
background-color: orange;
}
#purple {
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
width: 180px;
height: 180px;
background-color: purple;
}
#blue{
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
width: 80px;
height: 80px;
background-color: #58a;
}
style>
head>
<body>
<div id="orange">
<div id="purple">
<div id="blue">div>
div>
div>
<script>
document.getElementById('orange').addEventListener('click', function(){
console.log("我是橘色盒子");
}, false);
document.getElementById('purple').addEventListener('click', function(){
console.log("我是紫色盒子");
}, false);
document.getElementById('blue').addEventListener('click', ()=>{
console.log("我是蓝色盒子");
}, false);
script>
在页面中的显示如下图:
当我点击最中心的蓝色盒子时会打印的信息如下:
当我单击了页面中最中心的蓝色盒子时,click 事件会按照下面的顺序进行传播:
- div#blue
- div#purple
- div#orange
- body
- html
- doucument
也就是说 click
事件首先在最里面的蓝色盒子上发生,这个元素就是我们开始时单击的元素,然后 click
事件会沿着 DOM
树向上传播,直至传播到 document
对象:
如下图所示:
所有的现代浏览器都支持事件冒泡,但在具体的实现上还是有一些差别,IE5.5
及更早的版本中事件冒泡会跳过 元素(直接从
body
到 document
).IE9
FireFox
Chrome
和 Safari
则将事件一直冒泡到 window
对象.
Netspace
团队提出的另一种事件流叫做 事件捕获,事件捕获的思想是不太具体的节点应该更早的接收到事件,而最具体的节点应该最后接收到事件
事件默认是冒泡的,可通过 addEventListener 的第三个参数这只为 true 将事件设置为捕获,同时,添加了捕获的事件在使用 removeEventListener 移除时也需要写第三个参数 true
将上题中的代码事件监听中的第三个参数由 false
改为 true
,观察一下结果:
document.getElementById('orange').addEventListener('click', function(){
console.log("我是橘色盒子");
}, true);
document.getElementById('purple').addEventListener('click', function(){
console.log("我是紫色盒子");
}, true);
document.getElementById('blue').addEventListener('click', ()=>{
console.log("我是蓝色盒子");
}, true);
点击盒子后打印的信息如下:
我们单击了页面中的蓝色盒子,那么元素就会以下列顺序触发 click
事件
- document
- html
- body
- div#orange
- div#purple
- div#blue
在事件捕获过程中,document
对象首先接受到 click
事件,然后事件沿 dom树
依次向下,一直传播到事件的实际目标,即 div#blue
元素,下图就展示了事件捕获的过程:
DOM2级事件
规定的事件流包括三个阶段:
div#blue
盒子会按下图所示触发事件:DOM
事件流中,实际的目标元素(div#blue
)在捕获阶段不会接收到事件.这意味着在捕获阶段事件从哪个 window
到 document
再到 html
到 body
一直到 div#purple
就停止了,下一个阶段是 “处于目标阶段” ,于是在事件 div#blue
上发生,然后冒泡阶段发生,事件传回文档.默认事件 浏览器中很多操作都有默认的行为,比如右键打开菜单,我们称之为默认事件。
我们可以通过 DOM
中的事件对象来阻止默认事件.
阻止默认事件:
event
对象包含与创建它的特定事件有关的属性和方法,常见的如下:
阻止事件冒泡等.
属性/方法 | 类型 | 说明 |
---|---|---|
preventDefault | Function | 取消事件的默认行为. |
stopImmediate | Propagation() Function | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用 |
stopPropagation | Function | 取消事件的进一步捕获或冒泡. |
将最开始的案例阻止事件冒泡,改写如下代码:
document.getElementById('blue').addEventListener('click', (ev) => {
console.log("我是蓝色盒子");
}, false);
改为:
document.getElementById('blue').addEventListener('click', (ev) => {
ev.stopPropagation();
console.log("我是蓝色盒子");
}, false);
点击蓝色盒子后观察打印结果:
已经成功阻止事件冒泡.
在平时写的时候,我通常会将默认事件和事件冒泡都阻止,防止产生不必要的麻烦.
ev.stopPropagation();
ev.preventDefault();