参考资料:
理解事件冒泡和事件捕获
终于弄懂了事件冒泡和事件捕获!
JS阻止事件冒泡和默认事件
红皮书:
在第四代 Web 浏览器(IE4 和 Netscape Communicator 4)开始开发时,开发团队碰到了一个有意思的问题:页面哪个部分拥有特定的事件呢?
- 要理解这个问题,可以在一张纸上画几个同心圆。把手指放到圆心上,则手指不仅是在一个圆圈里,而且是在所有的圆圈里。两家浏览器的开发团队都是以同样的方式看待浏览器事件的。当你点击一个按钮时,实际上不光点击了这个按钮,还点击了它的容器以及整个页面。事件流描述了页面接收事件的顺序。
- 结果非常有意思,IE 和 Netscape 开发团队提出了几乎完全相反的事件流方案。IE 将支持事件冒泡流,而 Netscape Communicator 将支持事件捕获流。
事件冒泡: IE事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。
事件捕获: Netscape Communicator 团队提出了另一种名为事件捕获的事件流。事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标前拦截事件。
十分恰当的比喻: 当你把一个可以漂浮在水面上的物品,使劲向水里砸下去,它会首先有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后由于浮力大于物体自身的重力,物体会在到达最低点( 最具体元素)之后漂浮到水面上,这个过程相对于事件捕获是一个回溯的过程,即事件冒泡。
实例:
<body>
<div id="div-01">
start!
<h3 id="h3-01">hello</h3>
<h3 id="h3-02">world</h3>
</div>
</body>
<script>
let div = document.getElementById('div-01')
let h_1 = document.getElementById('h3-01')
let h_2 = document.getElementById('h3-02')
// 设置h_1、h_2为默认事件(事件冒泡)
h_1.addEventListener('click', function () {
console.log('hello')
})
h_2.addEventListener('click', function () {
console.log('world')
})
// 设置div为事件捕获,点击hello,预期输出:start!hello
// div.addEventListener(
// 'click',
// function () {
// console.log('start!')
// },
// true
// )
// 设置div为事件冒泡,点击world,预期输出:world start!
div.addEventListener(
'click',
function () {
console.log('start!')
},
false
)
</script>
注意点:
-
addEventListener的第三个参数决定了是事件捕获还是事件冒泡
true:表示注册的事件在捕获阶段触发
false:表示注册的事件在冒泡阶段触发-----默认值 -
误解:元素并不是只有注册了事件,才会有事件冒泡和事件捕获
-
事件流三个阶段; 1.事件捕获 2.目标事件[事件源] 3.事件冒泡
-
一个元素的事件,不会在两个阶段都触发
额外知识点: 阻止事件冒泡和默认事件
<body>
<div id="info">
<div class="box-1" οnclick="box1()">
最外层
<div class="box-2" οnclick="box2()">
第二层
<div class="box-3" οnclick="box3()">
第三层
<div class="box-4" οnclick="bubbles()">最底层</div>
</div>
</div>
</div>
</div>
</body>
<script>
function bubbles() {
console.log('最底层盒子被点击了')
}
function box1() {
console.log('最外层盒子被点击了')
}
function box2() {
console.log('第二个盒子被点击了')
}
function box3() {
console.log('第三个盒子被点击了')
}
</script>
阻止冒泡事件(最底层):
<script>
function bubbles(e) {
var ev = e || window.event
if (ev && ev.stopPropagation) {
//非IE浏览器
ev.stopPropagation()
} else {
//IE浏览器(IE11以下)
ev.cancelBubble = true
}
console.log('最底层盒子被点击了')
}
</script>
阻止默认事件的方式:
//谷歌及IE8以上
e.preventDefault();
//IE8及以下
window.event.returnValue = false;
//无兼容问题(但不能用于节点直接onclick绑定函数)
return false;
实例:
<body>
<div id="info">
<a href="www.baidu.com" id="test">阻止默认事件</a>
</div>
</body>
<script>
var aDom = document.getElementById('test')
aDom.onclick = function (e) {
if (e && e.preventDefault) {
//非IE浏览器
e.preventDefault()
} else {
//IE浏览器(IE11以下)
window.event.returnValue = false
}
//return false; //或者不写上面的判断直接写这句
}
</script>
使用return false注意点:
<body>
<div id="info">
<a href="www.baidu.com" οnclick="defaultEvent()">阻止默认事件</a>
</div>
</body>
<script>
function defaultEvent() {
return false
}
</script>
无法通过这个函数阻止a标签的跳转,return false不能适用于直接用onclick绑定的事件,所以当我们使用这种绑定事件方式时,我们还是需要采用e.preventDefault()这个函数
写在最后:return false 不仅阻止了事件往上冒泡,而且阻止了事件本身。event.stopPropagation() 则只阻止事件往上冒泡,不阻止事件本身。