前置概念:
在一个相互嵌套的父子dom元素中,给父子元素都绑定了【相同事件类型】的事件时,事件如何触发,由哪些dom元素触发?这些一系列的触发存在一套规则,这套规则便是js的事件传播机制。
案例
以下就是一个多级嵌套的html元素
<div class="father">
<div class="son">
<div class="sun"></div>
</div>
</div>
给三个div都绑定点击事件
const divs = document.getElementsByTagName('div')
divs[0].addEventListener('click', function () {
console.log('father');
})
divs[1].addEventListener('click', function () {
console.log('son');
})
divs[2].addEventListener('click', function () {
console.log('sun');
})
当我们点击sun这个div标签时,不论我们的dom元素在页面中是如何布局,三个点击事件都将触发。
注意!!! 事件触发的规则和页面布局毫无关系,只和我们的【html中的嵌套关系】决定
原因:
js事件传播方式(方向)有两种,分别是事件冒泡和事件捕获,两者传播方向相反
- 事件冒泡:结构上【而非视觉上】是嵌套关系的元素,设置同一事件 自子元素冒泡向父元素(自底向上)
- 事件捕获: 结构上【而非视觉上】是嵌套关系的元素,设置同一事件 自父元素捕获至子元素(自顶向下)
- 设置方式: 通过绑定事件的API【addEventListener】,传入第三个参数来设置传播方式,默认值为false(事件冒泡),true(事件捕获)
const divs = document.getElementsByTagName('div')
divs[0].addEventListener('click', function () {
console.log('father');
},true)
divs[1].addEventListener('click', function () {
console.log('son');
},true)
divs[2].addEventListener('click', function () {
console.log('sun');
},true)
当我们将三个div都设置成事件捕获后,能看到最先触发事件的是father这个div
我们能够发现,传播方式不同,事件执行的顺序也会不同,当我们打破常规,不再将三个div进行统一设置传播方式,而让二种方式都存在时,事件的触发顺序又将如何? 将son这个div设置为捕获,其他两div设置为冒泡,代码如下:
const divs = document.getElementsByTagName('div')
divs[0].addEventListener('click', function () {
console.log('father');
},false)
divs[1].addEventListener('click', function () {
console.log('son');
},true)
divs[2].addEventListener('click', function () {
console.log('sun');
},false)
点击sun这个div:
原因:
输出这样的打印结果是因为一个事件的触发,是由根节点html出发开始捕获,一直向内层寻找事件源,找到之后执行事件然后再回溯(冒泡)回根节点,因此事件触发有三个阶段:捕获阶段=>目标阶段=>冒泡阶段,先后顺序显而易见是捕获在先,冒泡在后。
阻止事件传播:
在不同的业务需求下,我们往往需要阻止非必要的事件传播,只希望当前元素才能触发事件。 事件对象概念:给事件处理函数传入一个参数,系统会事件对象赋给该参数,事件对象中包含事件的各种信息以及一些API,其中就有阻止事件传播的API 方法: 通过给事件处理器传入事件对象,调用事件对象的stopPropagation函数即可
divs[1].addEventListener('click', function (e) {
console.log('son');
e.stopPropagation()
},true)