主要讲解事件绑定和事件委托,onclick事件和addEventListener的区别
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.father {
width: 100%;
height: 300px;
display: flex;
}
.child {
width: 100px;
height: 100px;
background-color: aquamarine;
margin: 10px;
}
</style>
</head>
<body>
<div class="father">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
<div class="child">4</div>
<div class="child">5</div>
</div>
</body>
</html>
示例1:给元素绑定click
点击事件
使用onclick
- 重复绑定点击事件,后面的事件会覆盖前面的事件
<script>
const element = document.querySelector('.father')
element.onclick = () => {
console.log('点击事件1')
}
element.onclick = () => {
console.log('点击事件2')
}
// 控制台只会输出:点击事件2
</script>
使用addEventListener
- 重复绑定点击事件,事件不会被覆盖(类似于发布订阅模式,on中收集的事件存放在数组中,在emit时会遍历执行事件)
<script>
const element = document.querySelector('.father')
element.addEventListener('click', () => {
console.log('点击事件1')
})
element.addEventListener('click', () => {
console.log('点击事件2')
})
// 控制台输出 点击事件1 点击事件2
</script>
语法:document.addEventListener(event, function, useCapture)
useCapture
是可选的布尔值,指定事件是否在捕获或冒泡阶段执行
- true:事件在捕获阶段执行
- false:默认值,事件在冒泡阶段执行
element .addEventListener('click', (el) => {
console.log(el.currentTarget) // 当前绑定事件的元素
console.log(el.target) // 点击事件触发的元素
})
示例2:默认给每个child元素绑定一个点击事件,现在如果class名包含out-box的元素需要阻止之前绑定的点击事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.father {
width: 100%;
display: flex;
}
.child {
width: 100px;
height: 100px;
background-color: aquamarine;
margin: 10px;
}
</style>
</head>
<body>
<div class="father">
<div class="child">1
<div class="bady-one">1-1
<div class="yang-two">1-1-1</div>
</div>
</div>
<div class="child">2
<div class="bady-one out-box">2-1
<div class="yang-two">2-1-1</div>
</div>
</div>
<div class="child">3
<div class="bady-one">3-1
<div class="yang-two">3-1-1</div>
</div>
</div>
</div>
<script>
// 示例:默认给每个child元素绑定一个点击事件,现在如果class名包含out-box的元素需要阻止之前绑定的点击事件
const childElements = document.querySelectorAll('.child')
childElements.forEach(el => {
el.addEventListener('click', () => {
console.log('child点击事件')
})
}) // 默认第三个参数为false,在冒泡阶段执行
</script>
<script>
// 处理需求:在捕获阶段找到class名包含out-box的元素,并阻止冒泡
const fatherElement = document.querySelector('.father')
fatherElement.addEventListener('click', (el) => {
const curentElement = el.target // 点击事件触发的元素,如果不是child元素,需要再找到它的class为child的父元素
const parentElement = getParentElement(curentElement, 'child')
if (curentElement.classList.toString().indexOf('child') !== -1 || parentElement) {
if (curentElement?.querySelector('.out-box') || parentElement?.querySelector('.out-box')) {
// console.log('点击事件')
el.preventDefault() // 阻止默认行为
el.stopPropagation() // 阻止冒泡
}
}
}, true) // true点击事件在捕获阶段执行
// 获取一个元素指定的父元素,使用元素的parentElement属性和while循环向上遍历DOM树,直到找到指定的父元素为止
function getParentElement(target, className) {
let parent = target.parentElement
while (parent) {
if (parent.classList.toString().indexOf(className) !== -1) {
return parent
}
parent = parent.parentElement
}
return null
}
</script>
</body>
</html>
DOM事件流
事件触发经典案例
解析:前面提到的DOM事件流的执行顺序是先捕获再冒泡,所以dom事件流从外向内捕获过程就是grandma -> monther -> daughter -> baby,而只有monther和daughter设置了useCapture = true,所以在捕获阶段就先将事件处理了,而grandma和baby并未设置useCapture = true,默认是false,而我们又是点击的baby所以首先会先处理baby目标事件,然后再通过冒泡到grandma事件。