JavaScript事件流(冒泡、捕获)

引言 

我们给下面的四个圆圈都添加鼠标点击事件,要求鼠标点击时圆圈会变为粉色,效果如下:

但当我们直接点击最中间的圆圈时却发现所有的圆圈都会变色:

很多初学者可能会认为这是一种包含关系,但在 JavaScript 中这叫事件流。我们来看看点击事件的代码:

<!-- CSS 部分略 -->
<body>
    <div class="div1">
        <div class="div2">
            <div class="div3">
                <div class="div4"></div>
            </div>
        </div>
    </div>
    <script>
        let divs = document.querySelectorAll('div');
        for(let i=0; i<divs.length; i++) {
            divs[i].addEventListener('click', function() {
                this.style.backgroundColor = 'pink';
            }, false);  // 最后一个参数不写时默认为:false
        }
    </script>
</body>

由于 addEventListener() 中最后一个参数是 false, 我们直接点击最中间的圆圈时,事件触发的顺序就会变为:div4 > div3 > div2 > div1,这种行为就是事件流中的冒泡行为。

下面开始详细介绍事件流、事件捕获、事件冒泡以及利用它们的实际使用例子来加深理解。

事件流

什么是事件流?

事件流描述了页面接收事件的顺序,DOM2 Events 规范将事件流分为三个阶段:事件捕获到达目标事件冒泡

动图演示:

当 div 被点击时,不会直接触发事件,而是先执行捕获阶段。从整个文档最大的部分开始,到HTML元素,再到body元素,如果它们都有自己的事件,则在捕获期间,先按顺序执行它们的事件。到达目标时,才会触发点击对象的事件,这里的点击对象是 <div> 元素。最后,开始冒泡阶段,反向传递至文档最大的部分。

在实际使用时,不会同时触发捕获和冒泡行为,而是通过给事件监听器传入 true 和 false 来确定其中的一种:

  • true:事件捕获

  • false:事件冒泡

事件冒泡

定义:事件从最具体的元素(被点击的节点)开始触发,向上传播至没有那么具体的元素(文档)。

现在的浏览器,可以向上冒泡至 window 对象。

回到文章开头的例子,由于 addEventListener() 中最后一个参数是 false,表示以向上冒泡来执行事件,并且每个圆都被添加了鼠标点击事件,因此事件触发的顺序就会变为:div4 > div3 > div2 > div1,所对应的行为就是:最小的圆变色 > 稍微大一点的圆变色 > 第二大的圆变色 > 最大的圆变色。

事件冒泡的优点:当多个子元素拥有相同事件行为时,可以利用冒泡机制,让父级元素处理子元素的事件,这就是事件委托。只给父元素添加一个事件,而不需要给每个子元素添加事件,从而提高性能。用一个例子来演示一下:

<!-- CSS 部分略 -->
<body>
    <div>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
    </div>
    <script>
        let span = document.querySelectorAll('span');
        for(let i=0; i<span.length; i++) {
            span[i].addEventListener('click', function() {
                this.style.backgroundColor = "#ffb900";
            });
        }
    </script>
</body>

上面这个点击变色的案例,通过循环的方式给每个 span 元素添加鼠标点击事件,一共添加了4个。接下来利用事件委托的方式来小小的升级一下!

let div = document.querySelector('div');
div.addEventListener('click', function(event) {
     event.target.style.backgroundColor = "#ffb900";
});

这里给回调函数传入了一个事件对象 eventevent.target 表示的是事件目标,即我们点击的那个元素。这里,我们点击的是 span 元素,所以 event.target 指向的就是 span 元素。由于 span 元素并没有被添加事件监听器,所以不会有任何事件被触发,根据冒泡机制,向上触发事件的是 div 元素。div 元素的事件被触发后,如何判断点击的是哪个 span 呢?event.target 已经告诉它了!

如果还是不明白 event.target 在这里的作用,建议去了解下 JavaScript事件对象

事件冒泡的缺点:当父元素和子元素都添加了事件监听器,根据冒泡机制,点击子元素时,父元素的事件也会被触发。而我们只想触发子元素的事件,不想触发父元素的事件时,该怎么办?看下面这个例子:

<!-- CSS 部分略 -->
<body>
    <div>
        <span></span>
    </div>
    <script>
        let div = document.querySelector('div');
        let span = document.querySelector('span');
        span.addEventListener('click', function() {
            this.style.backgroundColor = "#ffb900";
        });
        div.addEventListener('click', function(event) {
            this.children[0].style.backgroundColor = "pink";
        });
    </script>
</body>

我想实现点击粉色区域变为橙色,点击绿色区域让橙色变为粉色。但上述代码似乎无法达到效果,这是因为点击粉色区域后,粉色区域会变为橙色,根据冒泡机制,向上触发绿色区域的点击事件,从而导致橙色又被变回粉色,所以看上似乎没有产生任何变化。这里的冒泡机制就成了累赘,我们需要阻止事件冒泡

span.addEventListener('click', function(event) {
    this.style.backgroundColor = "#ffb900";
    event.stopPropagation(); // 阻止事件冒泡
});

修改代码后,看看效果:

事件捕获

定义:事件从最不具体的元素开始执行(文档),向下传播至最具体的元素(被点击的元素)。

现在的浏览器,从 window 对象开始向下传递。

事件捕获和事件冒泡是一个完全相反的行为,但有一点需要注意!旧版本的浏览器(IE9之前)并不支持事件捕获

总结

  • 实际开发中,事件冒泡用的会更多,事件捕获一般用在特殊情况下。

  • 利用事件委托可以有效的提高程序性能。

  • DOM2 Events 规范将事件流分为三个阶段:事件捕获到达目标事件冒泡。并规定事件捕获阶段不触发目标事件,而事件冒泡阶段会在目标事件被触发后执行。但现在的浏览器并没有按照这个规定来实现事件流,在事件捕获阶段的终点,任然还是会触发目标事件,所以对于现在的浏览器而言,不管是捕获还是冒泡,目标事件都会被触发。

本文参考自《JavaScript高级程序设计》第4版

猜你喜欢

转载自blog.csdn.net/apple_54470279/article/details/124368971