js的事件冒泡和事件捕获

JavaScript事件

事件是文档和浏览器窗口中发生的特定的交互瞬间,我们与浏览器中web页面进行某些类型的交互时,事件就发生了。

事件可能是用户在某些内容上的点击,鼠标经过某个特定元素或按下键盘上的某些按键,事件还可能是web浏览器中发生的事情,比如说某个web页面加载完成,或者是用户滚动窗口或改变窗口大小。

JavaScript事件流

事件流描述的是从页面中接受事件的顺序,但有意思的是,微软(IE)和网景(Netscape)开发团队居然提出了两个截然相反的事件流概念,IE的事件流是事件冒泡流(event bubbling),而Netscape的事件流是事件捕获流(event capturing)

事件冒泡

事件冒泡由IE提出,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。利用该原理实现事件委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js的事件冒泡和事件捕获</title>
</head>
<body onclick="bodyClick()">
<div onclick="divClick()">
<p onclick="pClick()">
<button onclick="btnClick()">click</button>
</p>
</div>

<script>
function btnClick() {
console.log('button被点击');
}
function pClick() {
console.log('p被点击');
}
function divClick() {
console.log('div被点击');
}
function bodyClick() {
console.log('body被点击');
}
</script>
</body>
</html>

正如上面我们所说的,它会从一个最具体的的元素接收,然后逐级向上传播, button=>p=>div=>body…事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。

阻止事件冒泡

event.stopPropagation(); 或 event.stopImmediatePropagation(); 或 event.cancelBubble = true;

接着使用上面案例我们现在阻止它的冒泡行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>阻止冒泡行为</title>
</head>
<body onclick="bodyClick()">
<div onclick="divClick()">
<p onclick="pClick()">
<button onclick="btnClick()">click</button>
</p>
</div>

<script>
function btnClick() {
console.log('button被点击');
// event.cancelBubble = true;
event.stopPropagation();
}
function pClick() {
console.log('p被点击');
}
function divClick() {
console.log('div被点击');
}
function bodyClick() {
console.log('body被点击');
}
</script>
</body>
</html>

我们可以看到已经没有冒泡行为,需要注意的是前两个( stopPropagation()和stopImmediatePropagation() ) IE 低版本不支持。

事件捕获

事件捕获由网景提出,事件从最不具体的元素接收到最具体的元素接收

针对上面同样的例子,点击按钮,那么此时click事件会按照这样传播:(下面我们就借用addEventListener的第三个参数来模拟事件捕获流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js的事件冒泡和事件捕获</title>
</head>
<body>
<div>
<p>
<button>捕获</button>
</p>
</div>

<script>
const oB=document.querySelector('button');
const oP=document.querySelector('p');
const oD=document.querySelector('div');
const oBody=document.querySelector('body');

oB.addEventListener('click',function(){
console.log("button被点击")
},true);

oP.addEventListener('click',function(){
console.log('p标签被点击')
},true);

oD.addEventListener('click', function(){
console.log('div被点击')
},true);

oBody.addEventListener('click',function(){
console.log('body被点击')
},true);
</script>
</body>
</html>

正如我们看到的,和冒泡流万全相反,从最不具体的元素接收到最具体的元素接收事件 body=>div=>p=>button

阻止事件捕获

event.stopPropagation(); 或 event.stopImmediatePropagation();

还是使用上面捕获案例来阻止捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>阻止捕获</title>
</head>
<body>
<div>
<p>
<button>捕获</button>
</p>
</div>

<script>
const oB=document.querySelector('button');
const oP=document.querySelector('p');
const oD=document.querySelector('div');
const oBody=document.querySelector('body');

oB.addEventListener('click',function(){
console.log("button被点击")
},true);

oP.addEventListener('click',function(){
console.log('p标签被点击')
},true);

oD.addEventListener('click', function(){
console.log('div被点击')
},true);

oBody.addEventListener('click',function(){
console.log('body被点击');
event.stopPropagation()
// event.stopImmediatePropagation();
},true);
</script>
</body>
</html>

我们可以看到已经没有捕获行为,需要注意的是前两个( stopPropagation()和stopImmediatePropagation() ) IE 低版本不支持。

DOM事件流

‘DOM2级事件’规定的事件流包含3个阶段,事件捕获阶段、处于目标阶段、事件冒泡阶段。首先发生的事件捕获为截获事件提供机会,然后是实际的目标接收事件,最后一个阶段是事件冒泡阶段,可以在这个阶段对事件做出响应。

在DOM事件流中,事件的目标在捕获阶段不会接收到事件,这意味着在捕获阶段事件从document到 <button> 就停止了,下个阶段是处于目标阶段,于是事件在 <button> 上发生,并在事件处理中被看成冒泡阶段的一部分,然后,冒泡阶段发生,事件又传播回document。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM事件流</title>
</head>
<body>
<button id="btn">DOM事件流</button>

<script>
const btn=document.getElementById("btn");

btn.onclick=function(event){
console.log("div 处于目标阶段");
};

document.body.addEventListener("click",function(event){
console.log("event bubble 事件冒泡");
},false);

document.body.addEventListener("click",function(event){
console.log("event catch 事件捕获");
},true);
</script>
</body>
</html>

就是这样一个流程,先捕获,然后处理,然后再冒泡出去。

DOM 2级事件处理程序

DOM 2级事件定义了两方法:用于处理添加事件和删除事件的操作: 添加事件 addEventListener() 删除事件 removeEventListener()

所有DOM节点中都包含这两个方法,并且他们都包含3个参数:

  1. 要处理的事件方式(例如:click,mouseover,dbclick…..);
  2. 事件处理的函数,可以为匿名函数,也可以为命名函数(但如果需要删除事件,必须是命名函数);
  3. 一个布尔值,代表是处于事件冒泡阶段处理还是事件捕获阶段(true:表示在捕获阶段调用事件处理程序;false:表示在冒泡阶段调用事件处理程序)。

使用DOM 2级事件处理程序的主要好处是可以添加多个事件处理程序,事件处理会按照他们的顺序触发,通过addEventListener添加的事件只能用removeEventListener来移除,移除时传入的参数与添加时使用的参数必须相同,这也意味着添加的匿名函数将无法移除,(注意:我们默认的第三个参数都是默认false,是指在冒泡阶段添加,大多数情况下,都是将事件处理程序添加到事件的冒泡阶段,这样可以最大限度的兼容各个浏览器)

匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//这是一个DOM 2级事件 添加事件最简单的方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>

<script>
const btn=document.querySelector('button');

btn.addEventListener('click',function(){
console.log('我是按钮')
},false) //当第三个参数不写时,也是默认为false(冒泡时添加事件)
</script>
</body>
</html>

命名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>

<script>
const btn=document.querySelector('button');

btn.addEventListener('click',foo,false);

function foo(){
console.log('我是按钮')
}
//其实操作就是把写在里面的函数拿到了外面,而在原来的位置用函数名来代替
</script>
</body>
</html>

添加两个事件试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
const btn=document.querySelector('button');

btn.addEventListener('click',foo,false);
//第一个事件
function foo(){
console.log('我是按钮')
}
//第二个事件
btn.addEventListener('click',newFoo,false);
function newFoo(){
console.log('我是新按钮')
}
</script>
</body>
</html>

所以说,我们添加两个事件是可以的,事件的顺序就是按照我们程序写的顺序执行的

试试DOM 0级事件处理程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button onclick="foo()" onclick="newFoo()">按钮</button>

<script>
function foo(){
console.log('foo')
}

function newFoo(){
console.log('newFoo')
}
</script>
</body>
</html>

只执行了第一个事件,第二个被忽略,这并不是我们想要的结果,而addEventLiener是会把两个事件都去执行的。

扩展 - 阻止默认行为

preventDefault

preventDefault它是事件对象(Event)的一个方法,作用是取消一个目标元素的默认行为。既然是说默认行为,当然是元素必须有默认行为才能被取消,如果元素本身就没有默认行为,调用当然就无效了。什么元素有默认行为呢?如链接 <a> ,提交按钮 <input type=”submit”> 等。当Event对象的cancelable为false时,表示没有默认行为,这时即使有默认行为,调用 preventDefault也是不会起作用的。

我们都知道,链接 <a> 的默认动作就是跳转到指定页面,下面就以它为例,阻止它的跳转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="https://hutaoao.github.io" id="testA" >我是a标签</a>

<script>
const a = document.querySelector('#testA');
a.onclick =function(event){
//阻止默认行为
event.preventDefault();//效果同 return false
}
</script>
</body>
</html>

这时候点击 a 标签没有任何动作,不会跳转到对应连接,阻止了它的默认行为。

stopPropagation

stopPropagation也是事件对象(Event)的一个方法,作用是阻止目标元素的冒泡事件,但是会不阻止默认行为

return false

现在很多js代码都直接使用jQuery来写,在jQuery中使用return false时,相当于同时使用event.preventDefault和event.stopPropagation,它会阻止冒泡也会阻止默认行为。 但是使用原生js写时,return false只会阻止默认行为,不阻止冒泡。

谢谢阅读,以上借鉴自 https://www.cnblogs.com/christineqing/p/7607113.html

海盗船长 wechat
扫码关注我的公众号哟
0%