文章

js的事件冒泡和事件捕获

js的事件冒泡和事件捕获

js的事件冒泡和事件捕获

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>

1574646730367-d390e351-bcb5-408c-9328-bbc408d91d05.jpeg

正如上面我们所说的,它会从一个最具体的的元素接收,然后逐级向上传播, 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>

1574646747064-83455861-a76c-4f2b-ab05-f8a80c1e5549.jpeg

我们可以看到已经没有冒泡行为,需要注意的是前两个( 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>

1574646760921-b2fa8005-7133-4b4f-86a8-78e345022426.png

正如我们看到的,和冒泡流万全相反,从最不具体的元素接收到最具体的元素接收事件 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>

1574646784270-8a69773b-0ac3-4bfe-80bf-384eb3dafd23.jpeg

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

DOM事件流

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

在DOM事件流中,事件的目标在捕获阶段不会接收到事件,这意味着在捕获阶段事件从document到

1574646855588-d97784f9-8aeb-4d0e-a7f6-d4c8d15a4894.png

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>

1574646865554-4ca5243d-62ff-42f0-a1ed-ad1094f3ea0c.jpeg

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

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>

1574646882376-02f75dbe-10d7-428b-8928-65f9a7916529.jpeg

命名函数

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>

1574646893562-ba34c579-9bbc-4de0-9b5c-82fb6a93b04a.jpeg

添加两个事件试试

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>

1574646903187-f8ec13a8-7cab-4caf-9d0d-9feb02bbf367.jpeg

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

试试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>

1574646926455-9926a60d-1255-4539-a519-a27307a14c13.jpeg

只执行了第一个事件,第二个被忽略,这并不是我们想要的结果,而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

本文由作者按照 CC BY 4.0 进行授权