写在前面的话
其实很久之前就接触过DOM事件流了,但是在很长时间没有用到之后,就把它忘记了,所以今天特意来写一下,顺便回顾一下DOM时间流的一些知识
DOM事件流的分类
DOM事件流是有两种的,一种是捕获型事件流,另外一种是冒泡型事件流,两者其实都很好理解,下面我们就来详细介绍一下,为了大家更好的理解,我们就先介绍下冒泡型事件流
- 冒泡型事件流 "冒泡":没错就是你心中想的,冒泡就是我们平时可以常见的,例如水中的气泡往上冒,这就是冒泡,所以冒泡型事件流,就是当你点击目标元素的时候,当前所触发的一些事件会向父元素中传递,这就是所谓的事件冒泡,如果大家还是有不理解的地方,可以直接看后面的代码
- 捕获型事件流 捕获型事件流正好是与冒泡型事件流相反的,当你点击目标元素的时候,在该目标元素上点击触发的事件,会从父元素向下传递
- 其实,DOM标准规定事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。 事件捕获阶段:实际目标在捕获阶段不会接收事件。也就是在捕获阶段,事件从document到再到就停止了。 处于目标阶段:事件在上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。 冒泡阶段:事件又传播回文档。
要点
在我们讨论详细了解之前,我们首先要弄理清一些知识点
- 在事件流中我们必须要记住一个结论,先捕获后冒泡
- 冒泡是默认开启的,那么我们改如何开启捕获呢,这时我们可以去js圣经中查找一下,addEventListener是支持三个参数的
1. event: 当前事件是什么事件,click,onmouseover?
2. callback: 当前事件所执行的回调
3. option | useCapture: 第三个参数是比较复杂的,
有两种情况,当第三个参数只传一个Boolean值时,此时就可以打开捕获模式,
当然第三个参数还可以传入一个对象,这个大家可以去MDN上看一下,面试的时候还是会公司问的
options: {
capture
once: 是否只执行一次当前回调
passive:表示当前监听器永远不会调用preventDefault函数
最后一个option的话,因为有一些兼容性,就不在这里介绍了
}
代码
- 冒泡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div>
<p>
<a href="void: javascript(0)">点我让你看事件捕获</a>
</p>
</div>
</body>
<script>
$('div').click(function() {
console.log(2)
})
$('p').click(function() {
console.log(1)
})
$('a').click(function() {
console.log(0)
})
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div>
<p>
<a href="void: javascript(0)">
<span>123</span>
</a>
</p>
</div>
</body>
<script>
let div = document.getElementsByTagName('div')[0]
let p = document.getElementsByTagName('p')[0]
let a = document.getElementsByTagName('a')[0]
let span = document.getElementsByTagName('span')[0]
div.addEventListener("click", function() {
console.log(2)
})
p.addEventListener("click", function() {
console.log(1)
})
a.addEventListener("click", function() {
console.log(0)
}, true)
span.addEventListener("click", function() {
console.log(-1)
})
</script>
</html>
如何阻止?
阻止事件流
事件捕获我们可以通过addEventListener的第三个参数控制开关,但是事件冒泡我们该如何阻止呢,难道任由事件冒泡出现吗?幸运的是我们有stopPropagation这个函数,这个函数就是用来阻止事件冒泡的(其实也可以用来阻止事件捕获),先上代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div>
<p>
<a href="void: javascript(0)">
<span>123</span>
</a>
</p>
</div>
</body>
<script>
let div = document.getElementsByTagName('div')[0]
let p = document.getElementsByTagName('p')[0]
let a = document.getElementsByTagName('a')[0]
let span = document.getElementsByTagName('span')[0]
div.addEventListener("click", function() {
console.log(2)
})
p.addEventListener("click", function(e) {
e.stopPropagation()
console.log(1)
})
a.addEventListener("click", function(e) {
console.log(0)
// e.stopPropagation()
})
span.addEventListener("click", function() {
console.log(-1)
})
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div>
<p>
<a href="void: javascript(0)">
<span>123</span>
</a>
</p>
</div>
</body>
<script>
let div = document.getElementsByTagName('div')[0]
let p = document.getElementsByTagName('p')[0]
let a = document.getElementsByTagName('a')[0]
let span = document.getElementsByTagName('span')[0]
div.addEventListener("click", function() {
console.log(2)
})
p.addEventListener("click", function(e) {
e.stopPropagation()
console.log(1)
}, true)
a.addEventListener("click", function(e) {
console.log(0)
})
span.addEventListener("click", function() {
console.log(-1)
})
</script>
</html>
我们可以看到的是,控制台的打印是只有1的,这也就是说,这个函数不仅仅是用于阻止事件冒泡,它还可以用于阻止事件捕获,更可以说,事件流的三个阶段它都能阻止,这就是他的作用
那么除了使用stopPropagation()之外,我们还有其他的方法阻止吗,大家猜的不错,我们确实还有一种方法,来阻止事件流,那就是通过return false,这个方法是有一些缺陷的,他是只有在jQuery中才能生效,在原生js中只能是**阻止默认行为(注意这个名词,后面我们会提到)**的,先上代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div>
<p>
<a href="void: javascript(0)">点我让你看事件捕获</a>
</p>
</div>
</body>
<script>
$('div').click(function() {
console.log(2)
})
$('p').click(function() {
console.log(1)
return false
})
$('a').click(function() {
console.log(0)
})
</script>
</html>
阻止默认行为
上面我们在讲return false的时候,我们提到了一个名词,阻止默认行为,那么什么是默认行为呢,我们举个栗子,当我们点击a标签的时候,它会自动跳转到href对应的地址,这个跳转就叫默认行为,我们可以通过return false来阻止一些默认行为 除此之外,还有另一种方法就是通过调用preventDefault方法来实现,这个函数也是用来阻止默认事件的
注意点
我们在使用preventDefault和stopPropagation的时候,是会有兼容性的,在IE中是不存在这两个函数的,那么在IE中我们是如何实现阻止冒泡和阻止默认事件的呢,看代码
//阻止冒泡事件
function stopBubble(e){
if(e && e.stopPropagation){
// 非IE浏览器
e.stopPropagation();
}else{
//IE浏览器
window.event.cancelBubble=true;
}
}
// 阻止默认行为
//阻止浏览器默认行为
function stopDefault(e){
//标准浏览器
if(e && e.preventDefault){
e.preventDefault();
}
//个别IE
else{
window.event.returnValue=fale;
return false;
}
}
通过以上代码,我们就可以完美的实现阻止冒泡和默认行为了
存在的意义
在我们讨论了这么长的事件捕获和事件冒泡之后,我们要想一下,为什么会有事件捕获和事件冒泡呢 这就是我们接下来要讨论的事件代理(事件委托),
那么什么是事件委托呢,事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件(--- 源自JavaScript高级程序设计),
那么为什么会出现事件委托呢,当我们有1个li的时候,我们给li加上click事件,这样是完全没有问题的,但是当我们有成百上千个li呢,此时我们会怎么处理,当然一种最简单的方法就是for循环遍历,然后给每个li都加上click事件,这样确实能实现,但是我们要想到的是,html页面的渲染速度是和dom的操作的多少挂钩的,而dom操作的多少会和绑定的事件的数量挂钩的,绑定的数量越多,渲染肯定是越慢的,那么此时你肯定会问了,有什么好的解决方法吗,此时我们就可以用到这个名词了,事件委托,有限事件委托的原理就是事件冒泡,当我们给li添加事件的时候,此时事件流会顺着li向外层的ul流去,那么此时我们便可以只在ul上添加和li上相同的事件,便可以实现和在li上添加的相同的效果,我们先上代码看一下
html
<ul id="ul1">
<li>你好</li>
<li>你好</li>
<li>你好</li>
<li>你好</li>
<li>你好</li>
<li>你好</li>
<li>你好</li>
<li>你好</li>
</ul>
window.onload = function(){
var oUl = document.getElementById("ul1");
var aLi = oUl.getElementsByTagName('li');
for(var i=0;i<aLi.length;i++){
aLi[i].onclick = function(){
alert(123);
}
}
}
上面这种方式是给每个li添加事件,这个毫无疑问会降低渲染速度,下面我们看一下事件代理之后的代码
window.onload = function(){
var oUl = document.getElementById("ul1");
oUl.onclick = function(){
alert(123);
}
}
我们仅仅需要绑定一个事件,变可以实现相同的效果,岂不是美哉?,所以这就是我们常说的事件代理
======================================================================= 分割线,DOM事件流已经分析完毕了, 如果有什么不正确的地方欢迎大家指出来,我是一只在前端路上前进的程序袁,而且我会一直前进下去的,希望能和大家共同进步 欢迎大家进我的github上去看一下,我的github