1.事件:事件是指JS与HTML的交互。
2.事件流:IE的事件流是指事件冒泡,Netscape的事件流是指事件捕获。(Netscape是网景通信公司,曾是一家美国的计算机服务公司,曾是IE的竞争对手,在1998年11月被AOL收购)。事件冒泡是指自下而上,事件捕获是指自上而下。
3.DOM事件处理:DOM事件处理分为4个级别,分别是DOM0,DOM1,DOM2,DOM3,其中DOM1级事件处理标准中没有定义相关的内容,所以就没有所谓的DOM1事件处理。DOM3级事件处理是在DOM2级事件处理的基础上添加了更多的事件类型。下面我们就从DOM0和DOM2级事件处理来讲解JS事件流。DOM2把事件冒泡和事件捕获进行了结合,即事件流为:事件捕获阶段、处于目标阶段、事件冒泡阶段。
(1)DOM0级事件处理有两种事件绑定方法。第一是内联模型,第二是脚本模型。
内联模型是指直接在dom节点上绑定事件,比如<div οnclick="btnClick"></div>,而脚本模型就是我们最常见的,如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#son {
width: 50px;
height: 50px;
background: blue;
}
</style>
<script>
window.onload = function () {
let oSon = document.getElementById("son");
oSon.onclick = function () {
alert("son")
};
oSon.onclick = function () {
alert("son again");
}
}
</script>
</head>
<body>
<div id="son"></div>
</body>
</html>
在上面这个代码中,给son这个div绑定了两次点击事件,当点击这个div的时候,只会弹出son again。这是因为后面的事件把前面的事件给覆盖掉了。
再看下面这个代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#father {
width: 100px;
height: 100px;
background: red;
}
#son {
width: 50px;
height: 50px;
background: blue;
}
</style>
<script>
window.onload = function () {
let oSon = document.getElementById("son");
let oFather = document.getElementById("father");
oSon.onclick=function(){
alert("son")
};
oFather.onclick=function(){
alert("father")
}
}
</script>
</head>
<body>
<div id="father">
<div id="son"></div>
</div>
</body>
</html>
结果是先弹出son,再弹出father,这就是所谓的事件冒泡,但是却不能事件捕获(即只能自下而上,而不能自上而下)。
可以发现DOM0级事件处理存在的问题是:事件覆盖和不能事件捕获。当然它的优点是:极好的跨浏览器优势,会以最快的速度绑定。
(2)DOM2级事件处理就可以很好的处理DOM0级事件的问题。
首先我们处理一下DOM0级中的事件冲突:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#son {
width: 50px;
height: 50px;
background: blue;
}
</style>
<script>
window.onload = function () {
let oSon = document.getElementById("son");
let oFather = document.getElementById("father");
//通过addEventListener来绑定事件,其中第三个参数是个布尔型的,false代表事件冒泡,true代表事件捕获,在这里由于没有涉及到事件流,所以取什么都行
oSon.addEventListener("click", function () {
alert("son")
}, false);
oSon.addEventListener("click", function () {
alert("son again")
}, false);
}
</script>
</head>
<body>
<div id="son"></div>
</body>
</html>
先弹出son,再弹出son again。
接下来我们去解决事件捕获。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#father {
width: 100px;
height: 100px;
background: red;
}
#son {
width: 50px;
height: 50px;
background: blue;
}
</style>
<script>
window.onload = function () {
let oSon = document.getElementById("son");
let oFather = document.getElementById("father");
//通过addEventListener来绑定事件,其中第三个参数是个布尔型的,false代表事件冒泡,true代表事件捕获
oSon.addEventListener("click",function(){
alert("son")
},true)
oFather.addEventListener("click",function(){
alert("father")
},true)
}
</script>
</head>
<body>
<div id="father">
<div id="son"></div>
</div>
</body>
</html>
这次是先弹出father,再弹出son。这样就实现了事件捕获。当然如果想实现事件冒泡,将true改为false即可。
3.如何阻止事件冒泡和事件捕获。
如果是阻止事件冒泡,只需在son的点击事件中添加event.stopPropagation()即可(这在DOM0级事件处理中同样可以);如果是阻止事件捕获,只需在father的点击事件中添加event.stopPropagation()即可。
4.事件冒泡带来的好处就是可以使用addEventListener来进行事件委托,事件委托就是指自己的事情让别人替你做。优点是:(1)比如给一个ul下的各个li都添加一个事件,不仅繁琐又耗性能,这时就可以将事件委托给ul去做。(2)比如新增dom节点,然后给新增的dom节点绑定事件,那么同样可以使用事件委托。在jQuery中,是通过 $("ul").delegate("li","click",function(){ console.log($(this).html());})来实现的。
下面我们来研究一下事件冒泡和事件捕获的执行顺序。W3C规定,在冒泡和捕获事件同时存在的情况下,先执行捕获。下面这个代码如果点击son这个div会弹出什么?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#grandfather {
width: 150px;
height: 150px;
background: green;
}
#father {
width: 100px;
height: 100px;
background: red;
}
#son {
width: 50px;
height: 50px;
background: blue;
}
</style>
<script>
window.onload = function () {
let oSon = document.getElementById("son");
let oFather = document.getElementById("father");
let oGrandfather = document.getElementById("grandfather");
//通过addEventListener来绑定事件,其中第三个参数是个布尔型的,false代表事件冒泡,true代表事件捕获
oSon.addEventListener("click", function () {
alert("son 捕获")
}, true)
oFather.addEventListener("click", function () {
alert("father 冒泡")
}, false)
oGrandfather.addEventListener("click", function () {
alert("grandfather 捕获")
}, true)
}
</script>
</head>
<body>
<div id="grandfather">
<div id="father">
<div id="son"></div>
</div>
</div>
</body>
</html>
答案是:依次弹出grandfather 捕获、son 捕获、father 冒泡。因为点击son后,首先会找这个son的捕获事件,就是自己和grandfather都是捕获,那么捕获是从大到下来的,故先grandfather 捕获、son 捕获,最后再去执行冒泡,也就是father 冒泡。再看一下下面这个。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#grandfather {
width: 150px;
height: 150px;
background: green;
}
#father {
width: 100px;
height: 100px;
background: red;
}
#son {
width: 50px;
height: 50px;
background: blue;
}
</style>
<script>
window.onload = function () {
let oSon = document.getElementById("son");
let oFather = document.getElementById("father");
let oGrandfather = document.getElementById("grandfather");
//通过addEventListener来绑定事件,其中第三个参数是个布尔型的,false代表事件冒泡,true代表事件捕获
oSon.addEventListener("click", function () {
alert("son 冒泡")
}, false)
oSon.addEventListener("click", function () {
alert("son 捕获")
}, true)
oFather.addEventListener("click", function () {
alert("father 捕获")
}, true)
oFather.addEventListener("click", function () {
alert("father 冒泡")
}, false)
oGrandfather.addEventListener("click", function () {
alert("grandfather 捕获")
}, true)
oGrandfather.addEventListener("click", function () {
alert("grandfather 冒泡")
}, false)
}
</script>
</head>
<body>
<div id="grandfather">
<div id="father">
<div id="son"></div>
</div>
</div>
</body>
</html>
就是给三个div分别添加捕获和冒泡事件,点击son这个div会弹出什么呢?答案是:grandfather 捕获、father 捕获、son 冒泡、son 捕获、father 冒泡、grandfather 冒泡。为什么?因为点击son这个div,首先响应它祖先的捕获事件,从大到小依次是grandfather 捕获、father 捕获,接下来发现son身上有两个事件,一个是冒泡,一个是捕获,那么就按代码的上下顺序来执行,即先执行son 冒泡,再执行son 捕获,最后就是father 冒泡、grandfather 冒泡。如果点击father这个div,那么弹出的是grandfather 捕获、father 捕获(因为它在冒泡的前面)、father 冒泡、grandfather 冒泡。