前言
Javscript的事件机制可以分为捕获结合冒泡两个阶段,捕获阶段事件由外向内寻找dom上的绑定事件并执行,冒泡阶段从内向外寻找dom上的绑定事件并执行。在浏览器中javascript总是先进行捕获再进行冒泡。
<!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: 100px;
height: 100px;
background: yellow;
display: flex;
align-items: center;
justify-content: center;
}
.child {
width: 50px;
height: 50px;
background: palevioletred;
}
</style>
</head>
<body>
<div class="father" id="father">
<div class="child" id="child">
</div>
</div>
<script>
function childClick() {
console.log('child被点击了');
};
function fatherClick() {
console.log('father被点击了');
};
function bindClickEvent(el, fn, useCapture) {
el?.addEventListener('click', fn, useCapture);
};
bindClickEvent(document.getElementById('child'), childClick);
bindClickEvent(document.getElementById('father'), fatherClick);
</script>
</body>
</html>
当点击child的时候输出结果如上图,这是因为js中的事件默认都是冒泡类型的事件。father和child的事件都是在冒泡阶段执行的,冒泡是从里往外的,因为先执行child的点击事件再执行father的点击事件。
使用addEventListener声明捕获阶段的事件
前面我们提到默认事件都是冒泡阶段的事件,但是通过addEventListener可以声明捕获阶段的事件。
<script>
function childClick() {
console.log('child被点击了');
};
function fatherClick() {
console.log('father被点击了');
};
function bindClickEvent(el, fn, useCapture) {
el?.addEventListener('click', fn, useCapture);
};
bindClickEvent(document.getElementById('child'), childClick);
bindClickEvent(document.getElementById('father'), fatherClick,true);
</script>
addEventListener的第三个参数默认为false,即默认是冒泡事件,我们给father的点击事件设置成捕获阶段再次点击child:
可以看到执行顺序改变了,这是因为father是捕获事件,child是冒泡事件,浏览器先执行捕获事件再执行冒泡事件。
那我们将father和child都设置为捕获事件呢?结果还是先输出father再输出child,这是因为当child和father绑定的都是捕获事件,捕获是从外向内的。
利用事件冒泡实现事件委托
将事件委托给ul,通过e.target.id去识别点击的是哪个li
<ul onclick="handleClick(event)">
<li id="a" data-text="a"> 文本1 </li>
<li id="b" data-text="b"> 文本2 </li>
<li id="c" data-text="c"> 文本3 </li>
<li id="d" data-text="d"> 文本4 </li>
<li id="e" data-text="e"> 文本5 </li>
</ul>
<script>
function handleClick(e){
const id = e.target.id;
document.getElementById(id).style.color='red';
};
</script>
这里提一下e.target和e.currentTarget的区别:
1.在没有触发冒泡的时候,e.target和e.current相同
2.当触发冒泡的时候,e.target指向触发点击事件的子元素,currentTarget指向父元素
阻止冒泡
在前面的例子中我们知道当父元素和子元素同事绑定了点击事件的时候,点击子元素的时候父元素的事件也会执行。怎么样才能只执行子元素的点击事件呢?答案就是给child的点击事件设置阻止冒泡。
function childClick(e) {
const event = e || window.event;
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
};
console.log(‘child被点击了’);
};
再次点击child的时候就只会执行childClick事件了
addEventListener的具体使用
- 设置捕获事件
addEventListener('click',func,true);
addeventListener的第三个参数还可以是一个Object
addEventListener('click',func,{
capture:false,
once: false.
passive:false,
});
- capture : Boolean,是否将事件传递到捕获阶段
- once:Boolean ,开启后事件只执行一次
- passive: 开启后事件内不会再执行prevent,经常用于移动端touch事件的优化
这个三个option的默认值都是false
- 同一个元素上绑定多个事件
使用onclick每个元素最多绑定一个点击事件,使用addEventListener可以同时绑定多个事件,而且不会影响通过onclick绑定的事件
<script>
const father = document.getElementById('father');
function handleClick(){
console.log('通过onclick绑定的事件');
}
father.addEventListener('click',function(){
console.log('第1个点击事件');
});
father.addEventListener('click',function(){
console.log('第2个点击事件');
});
father.addEventListener('click',function(){
console.log('第3个点击事件');
});
</script>
总结
- Js的事件机制分为捕获和冒泡两个阶段,先进行捕获在进行冒泡,捕获是从外向内的,冒泡是从内向外的。
- Js的点击事件默认都是在冒泡阶段执行,通过设置addEventListener的第三个参数设置捕获事件。
- 合理的运用事件委托机制是个不错的选择,可以使用stopPropagation或者cancelBubble阻止冒泡。