事件委托是不在目标dom上绑定触发事件,而委托给在目标dom上层。这样做可以减少事件的注册,节省内存(比如在ul代理li的click事件),优化dom节点,当在ul中增加新的li时,不需要再去绑定click事件。
事件委托,首先要了解事件传播的过程 捕获 和 冒泡
事件传播
事件传播分为三个阶段
- 捕获阶段:事件从上层节点传到目标节点,称为捕获阶段(capture phase)
- 目标阶段:在目标节点上触发,称为目标阶段(target phase)。
- 冒泡阶段:从目标节点传回到上层节点,曾为冒泡阶段(bubbing phase)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<style>
.child {
background-color: silver;
margin: 10px;
}
</style>
<title>Document</title>
</head>
<body>
<div
id="grandfather"
class="grandfather"
style="height:200px;width:400px;background-color: indianred"
>
grandfather
<ul
id="father"
class="father"
style="margin:40px;background-color: cornflowerblue"
>
<li id="child1" class="child">child1</li>
<li id="child2" class="child">child2</li>
<li id="child3" class="child">child3</li>
<li id="child4" class="child">child4</li>
</ul>
</div>
<script src="./index.js"></script>
</body>
</html>
js
const grandfather = document.querySelector('#grandfather'),
father = document.querySelector('#father'),
childern = document.querySelectorAll('.child');
grandfather.addEventListener('click', function(e) {
console.log(`grandfather id: ${this.id} targetId: ${e.target.id}`);
});
father.addEventListener('click', function(e) {
console.log(`father id: ${this.id} targetId: ${e.target.id}`);
});
if (childern && childern.length) {
for (const child of childern) {
child.addEventListener('click', function(e) {
console.log(`child id: ${this.id} targetId: ${e.target.id}`);
});
}
}
- 当点击红色的grandfather区域时
输出结果
grandfather id: grandfather targetId: grandfather - 当点击蓝色father区域时
输出结果
father id: father targetId: father
grandfather id: grandfather targetId: father - 当点击银色child区域时
输出结果
child id: child2 targetId: child2
father id: father targetId: child2
grandfather id: grandfather targetId: child2
addEventListener还可以传入第三个参数,默认是false(在冒泡阶段触发),true(在捕获阶段触发)
可以看到当我们点击child2时,father和grandfather的click事件也会触发,this指向的是绑定的dom,但是e.target始终指向的目标节点,所以根据这个结果,可以把child的click事件委托给father或者grandfather
事件委托
我们可以将li(child)的click事件委托给ul(father),这样就不需要在每个li上都绑定一个click事件了,把child的click事件注释掉就可以了
const grandfather = document.querySelector('#grandfather'),
father = document.querySelector('#father'),
childern = document.querySelectorAll('.child');
grandfather.addEventListener(
'click',
function(e) {
console.log(`grandfather id: ${this.id} targetId: ${e.target.id}`);
}
);
father.addEventListener('click', function(e) {
console.log(`father id: ${this.id} targetId: ${e.target.id}`);
});
// if (childern && childern.length) {
// for (const child of childern) {
// child.addEventListener('click', function(e) {
// console.log(`child id: ${this.id} targetId: ${e.target.id}`);
// });
// }
// }
当再点击child2时
输出的结果
father id: father targetId: child2
grandfather id: grandfather targetId: child2
阻止事件的继续传播
事件委托给我带来很多方便,但是当事件向上冒泡时可能触发我们不想要的触发结果,所以我们还可以阻止事件的传播,应用方法
e.stopPropagation();
const grandfather = document.querySelector('#grandfather'),
father = document.querySelector('#father'),
childern = document.querySelectorAll('.child');
grandfather.addEventListener('click', function(e) {
console.log(`grandfather id: ${this.id} targetId: ${e.target.id}`);
});
father.addEventListener('click', function(e) {
console.log(`father id: ${this.id} targetId: ${e.target.id}`);
// 阻止事件传播
e.stopPropagation();
});
// if (childern && childern.length) {
// for (const child of childern) {
// child.addEventListener('click', function(e) {
// console.log(`child id: ${this.id} targetId: ${e.target.id}`);
// });
// }
// }
当再点击child2时
输出的结果
father id: father targetId: child2