什么是事件模型
Dom标准的事件模型:当事件发生时,其实会经历三个阶段!
- 由外向内的捕获阶段:从Dom树根节点开始,到当前点击的元素位置,遍历并记录当前元素各级父元素上绑定的事件处理函数,这个阶段只是记录并不触发。
- 目标触发阶段:优先触发当前点击元素的处理函数
- 冒泡执行阶段:按捕获阶段记录的各个父元素上的事件处理函数顺序,由内到外的反向的触发各级父元素上的事件
如图所示
看个例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
#d1 #d2 #d3 {
cursor: pointer;
}
#d1 {
background-color: green;
position: relative;
width: 150px;
height: 150px;
text-align: center;
cursor: pointer;
}
#d2 {
background-color: blue;
position: absolute;
top: 25px;
left: 25px;
width: 100px;
height: 100px;
}
#d3 {
background-color: red;
position: absolute;
top: 25px;
left: 25px;
width: 50px;
height: 50px;
line-height: 50px;
}
</style>
<body>
<div id="d1">
1
<div id="d2">
2
<div id="d3">3</div>
</div>
</div>
</body>
</html>
网页效果图
如果我在每个div上绑定一个点击事件,点到谁就弹窗内容为疼,JavaScript代码如下:
<script>
var d1=document.getElementById("d1");
var d2=document.getElementById("d2");
var d3=document.getElementById("d3");
//本意: 点谁,谁就喊疼!
d1.onclick=function(){
alert("d1疼!");
}
d2.onclick=function(){
alert("d2疼!");
}
d3.onclick=function(){
alert("d3疼!");
}
</script>
需求是点1绿色的div应该只弹出“d1疼”,点2蓝色的div应该只弹出“d2疼”,点3红色的div应该只弹出“d3疼”。
但是由于事件模型是冒泡执行的,如果点2蓝色的,就会弹出“d2疼”和“d1疼”。这样就导致了事件冒泡。
阻止冒泡/停止蔓延
使用事件对象中的stopPropagation()方法,来阻止冒泡/停止蔓延。e.stopPropagation()防的是父元素上处理函数的执行,而不是防当前元素自己的处理函数
所以想要实现点是谁才谁弹窗,如下代码。
<script>
var d1=document.getElementById("d1");
var d2=document.getElementById("d2");
var d3=document.getElementById("d3");
//本意: 点谁,谁就喊疼!
//当事件发生时 event
// ↓
d1.onclick=function(e){
//e.stopPropagation() //因为d1外边没有可执行的处理函数了
alert("d1疼!");
}
d2.onclick=function(e){
//e.stopPropagation() //都行
alert("d2疼!");
e.stopPropagation() //都行
}
d3.onclick=function(e){
e.stopPropagation()
alert("d3疼!");
}
</script>
这样就实现了我们需求了。
但是,冒泡这种机制到底好不好?如果不好为什么事件模型默认的就是冒泡呢?接下来我们看看冒泡的作用
事件委托/代理
什么是事件委托
如果多个平级子元素都要绑定相同的事件时,应该只给父元素绑定一次事件,所有子元素可通过冒泡机制共用父元素上的一个事件处理函数!
为什么要用事件委托
因为浏览器触发事件,是通过遍历事件监听队列的凡是查找符合条件的监听对象触发的。如果事件监听对象很多!遍历就会慢,导致事件响应变慢,
如何使用事件委托呢?我们用一个简单的手风琴为例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
ul {
list-style: none;
}
li:first-child {
border-top-left-radius: 7px;
border-top-right-radius: 7px;
}
li:nth-child(7) {
border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px;
}
li {
width: 200px;
height: 50px;
line-height: 50px;
border: 1px solid #ddd;
background-color: #fff;
}
.none {
display: none;
}
.block {
display: block;
}
</style>
<body>
<ul>
<li>手风琴1</li>
<span class="none">手风琴1的内容</span>
<li>手风琴2</li>
<span class="none">手风琴2的内容</span>
<li>手风琴3</li>
<span class="none">手风琴3的内容</span>
<li>手风琴4</li>
<span class="none">手风琴4的内容</span>
</ul>
<script>
var ul = document.querySelector("ul"); //获取父元素
//给父元素绑定一个点击事件
ul.onclick = function (e) {
//e.target专门保存最初点击的目标元素的特殊属性。一旦保存住最初点击的目标元素,就不会随冒泡而改变!
if (e.target.nodeName == "LI") {
var span = e.target.nextElementSibling;
var b_span = document.querySelector(".block");
if (span.className == "block") {
span.className = "none";
} else {
if (b_span != null) {
b_span.className = "none";
span.className = "block";
}
span.className = "block";
}
}
};
</script>
</body>
</html>
效果如下
效果 1 | 效果 2 | 效果3 |
---|---|---|
![]() | ![]() | ![]() |
将子元素触发相同的事件处理函数绑定在父元素身上,通过事件冒泡来获取触发事件函数,使用e.target来指向当前点击的元素,使当前点击的元素能够触发该事件。这就是事件委托