3. 事件周期
还记得 addEventListener() 方法的第三个参数吗?这个参数表示是捕获阶段还是冒泡阶段,默认false冒泡阶段。根据 W3C 标准事件的发生流程可以分为捕获阶段、触发阶段以及冒泡阶段。
- 捕获阶段: 事件根据 DOM 树结构从最上层节点向下传播,直到绑定该事件节点为止。
- 触发阶段: 事件发生,执行对应的处理函数的逻辑代码。
- 冒泡阶段: 事件根据 DOM 树结构从绑定事件节点向上传播。
假设一个元素div,它有一个下级元素p。
<div>
<p>元素</p>
</div>
这两个元素都绑定了click事件,如果用户点击了p,它在div和p上都触发了click事件,那这两个事件处理程序哪个先执行呢?事件顺序是什么?
(或者可以想象画在一张纸上的一组同心圆,如果你把手指放在圆心上,那么你的手指指向的其实不是一个圆,而是纸上所有的圆。放到实际页面中就是,你点击一个按钮,事实上你还同时点击了按钮所有的父元素。
开发团队的问题就在于,当点击按钮时,是按钮最外层的父元素先收到事件并执行,还是具体元素先收到事件并执行)
因为有两种观点,所以事件流也有两种,分别是事件冒泡和事件捕获。现行的主流是事件冒泡。
冒泡事件:
举个例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件周期</title>
<style>
#d1, #d2, #d3 {
cursor: pointer;
}
#d1 {
width: 300px;
height: 300px;
position: relative;
background: lightskyblue;
}
#d2 {
width: 200px;
height: 200px;
margin: 50px;
position: absolute;
background: lightgreen;
}
#d3 {
width: 100px;
height: 100px;
margin: 50px;
background:blueviolet;
}
</style>
</head>
<body>
<div id="d1">
d1
<div id="d2">
d2
<div id="d3">d3</div>
</div>
</div>
</body>
<script>
var d1=document.getElementById("d1");
d1.addEventListener("click",function () {
console.log("我d1被点击了");
},true);
var d2=document.getElementById("d2");
d2.addEventListener("click",function () {
console.log("我d2被点击了");
},false);
var d3=document.getElementById("d3");
d3.addEventListener("click",function () {
console.log("我d3被点击了");
},false);
</script>
</html>
当我点击d3时,效果图如下:
click事件首先在 d3 元素上发生,然后逐级向上传播。这就是事件冒泡。
事件捕获
事件捕获的概念,与事件冒泡正好相反。它认为当某个事件发生时,父元素应该更早接收到事件,具体元素则最后接收到事件。比如说刚才的demo,如果是事件捕获的话,事件发生顺序会是这样的:
d1->d2->d3
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件周期</title>
<style>
#d1, #d2, #d3 {
cursor: pointer;
}
#d1 {
width: 300px;
height: 300px;
position: relative;
background: lightskyblue;
}
#d2 {
width: 200px;
height: 200px;
margin: 50px;
position: absolute;
background: lightgreen;
}
#d3 {
width: 100px;
height: 100px;
margin: 50px;
background:blueviolet;
}
</style>
</head>
<body>
<div id="d1">
d1
<div id="d2">
d2
<div id="d3">d3</div>
</div>
</div>
</body>
<script>
var d1=document.getElementById("d1");
d1.addEventListener("click",function () {
console.log("我d1被点击了");
},true);
var d2=document.getElementById("d2");
d2.addEventListener("click",function () {
console.log("我d2被点击了");
},true);
var d3=document.getElementById("d3");
d3.addEventListener("click",function () {
console.log("我d3被点击了");
},true);
</script>
</html>
当点击d3时,效果图如下:
事件捕获阶段
也就是说,当事件发生时,首先发生的是事件捕获,为父元素截获事件提供了机会。
例如我们把d1的addEventListener,最后一个参数设置为true,捕获阶段;
<script>
var d1=document.getElementById("d1");
d1.addEventListener("click",function () {
console.log("我d1被点击了");
},true);
var d2=document.getElementById("d2");
d2.addEventListener("click",function () {
console.log("我d2被点击了");
},false);
var d3=document.getElementById("d3");
d3.addEventListener("click",function () {
console.log("我d3被点击了");
},false);
</script>
效果图如下:
可以看到,点击事件先被父元素截获了,且该函数只在事件捕获阶段起作用。
处于目标与事件冒泡阶段
事件到了具体元素时,在具体元素上发生,并且被看成冒泡阶段的一部分。随后,冒泡阶段发生,事件开始冒泡。
阻止事件冒泡
如果在上述示例中的冒泡阶段,我们可以看到事件会从最底层节点向上传播。如果只想触发当前节点的事件,而不继续向上冒泡,我们可以通过 Event 事件对象提供的属性来完成:
- IE 9 及之后版本和其他浏览器: stopPropagation() 方法
- IE 8 及之前版本的浏览器: cancelBubble 属性
<script>
var d1=document.getElementById("d1");
d1.addEventListener("click",function () {
console.log("我d1被点击了");
},true);
var d2=document.getElementById("d2");
d2.addEventListener("click",function () {
console.log("我d2被点击了");
},false);
var d3=document.getElementById("d3");
d3.addEventListener("click",function (event) {
event.stopPropagation();
console.log("我d3被点击了");
},false);
</script>
点击d3时,效果图:
不难看出,事件在到达具体元素后,停止了冒泡。但不影响父元素的事件捕获。
4. 事件委托
在讨论什么是事件委托之前,我们先来看一个示例:
<div class="container"> <button id="btn" class="btn btn-primary">添加</button> <ul id="parent" class="list-group"> <li class="list-group-item"><a href="#">链接</a></li> <li class="list-group-item"><a href="#">链接</a></li> <li class="list-group-item"><a href="#">链接</a></li> </ul> </div>
我们为上述页面的 <a>
标签绑定 onclick 事件:
var allA = document.getElementsByTagName('a'); for (var i=0;i<allA.length;i++){ allA[i].onclick = function(){ alert('我是一个链接.') } }
当动态为 <ul>
标签添加子标签 <li>
时,如下代码:
var btn = document.getElementById('btn'); btn.onclick = function(){ var ul = document.getElementById('parent'); var li = document.createElement('li'); li.setAttribute('class','list-group-item'); li.innerHTML = '<a href="#">链接</a>'; ul.appendChild(li); }
我们会发现,新添加的选项的 <a>
标签并没有 onclick 事件。如果我们想为新添加的 <a>
标签同样绑定 onclick 事件,需要在添加之前就完成事件绑定。除了上述方式可以解决这个问题之外,我们还可以将事件绑定到所有 <a>
标签共有的祖先元素上。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件委托</title>
<link rel="stylesheet" href="css/style.css">
<style>
body {
padding: 50px;
}
</style>
</head>
<body>
<div class="container">
<button id="btn" class="btn btn-primary">添加</button>
<ul id="parent" class="list-group">
<li class="list-group-item"><a href="#">链接</a></li>
<li class="list-group-item"><a href="#">链接</a></li>
<li class="list-group-item"><a href="#">链接</a></li>
</ul>
</div>
<script>
var btn =document.getElementById("btn");
btn.onclick=function () {
var ul=document.getElementById("parent");
var childLi=document.createElement("li");
childLi.setAttribute("class","list-group-item");
childLi.innerHTML="<a href=\"#\">链接</a>";
ul.appendChild(childLi);
}
var ul=document.getElementById("parent");
ul.onclick=function (event) {
var curEle=event.target;
if (curEle.nodeName=="A"){
console.log("dosomething");
}
}
</script>
</script>
</body>
</html>
这种将事件绑定其祖先元素的方式,我们可以称之为事件委托。
滚轮事件
onscroll事件 - 拖动滚动条,鼠标的滚轮 // IE8及以下版本浏览器不支持 - 并不报错,也是无效的
onmousewheel事件 - 鼠标的滚轮