事件监听器的第三个参数?一篇文章带你透彻理解JS的事件流

前言

在原生JS开发的那个"遥远年代",当我们试图监听用户的操作从而与用户进行交互的时候,经常需要我们去获取页面的dom元素并添加事件监听器。但是随着互联网的高速发展,单纯地通过获取dom元素添加事件监听器已经无法满足用户需求了,这个时候就需要去深入理解JS事件流这一特性,以帮助我们更好地完成开发。

正文

捕获与冒泡

若要各位读者老爷讲明白捕获和冒泡这两个概念,就不得不掏出我用了多年的一个小小demo了

html 复制代码
<div id="app">
    <div id="wrap">
        <div id="box"></div>
    </div>
</div>
css 复制代码
        #app {

            width: 400px;

            height: 400px;

            background-color: #ca6363;

        }

        #wrap {

            width: 200px;

            height: 200px;

            background-color: blueviolet;

        }

        #box {

            height: 100px;

            width: 100px;

            background-color: chartreuse;

        }

在这个demo中,很明显看到三个div,且由下到上逐渐减小,现在我给这三个容器添加点击事件

js 复制代码
const app = document.getElementById('app')
const wrap = document.getElementById('wrap')
const box = document.getElementById('box')

app.addEventListener('click', () => { console.log('app'); })
wrap.addEventListener('click', () => { console.log('wrap'); })
box .addEventListener('click', () => { console.log(box ); })

那么,现在当我点击一下最顶上的box会发生什么?

很明显,在浏览器看来,我同时点到了他们仨,这合情合理,毕竟你都点在我肾上了你总不能说没点我身上吧?那么,问题来了,为什么会是这么个打印顺序?你可能会说:这不是理所应当的吗?点击肯定是先点到最上面的,所以就先触发最上面的事件监听器打印box咯。但事实相反,当你点击的那一瞬间,你的点击事件是从最底层的window开始,传到html上,再到app,wrap,最后到顶层的box上面,而这一从底到顶的传播,就叫捕获。那既然是从底到顶,为什么事件的触发不是从底到顶呢。因为在window上往事件触发处传播,遇到注册的捕获事件会触发,简单点说,就是在捕获的过程中遇到的事件都是不可以在捕获阶段被触发的。

捕获结束后,也就到了最顶端容器,这个时候在一层层往window回去,直到回到window的过程就叫冒泡。在这个过程中遇到的事件就会触发掉。所以这也是为什么我们看到的打印顺序是app -> wrap -> box了。

事件监听器的第三个参数

在我刚刚的小demo中给出的事件监听器以及我们常用的事件监听器都是只有两个参数,但实际上,addEventListener还可以接受第三个参数true或false。那这个参数是用来干什么的?在刚刚聊到的捕获和冒泡中,我提到之所以在捕获的过程中不会触发函数是因为遇到的事件都是不可以在捕获阶段被触发的,而造成这一点的原因就是addEventListener的第三个参数默认为false,那如果我们都给设置为true,意味着使其变成能够在捕获阶段被触发,打印结果是否会按照捕获的顺序来呢?还是那句话,实践出真知

js 复制代码
app.addEventListener('click', () => { console.log('app'); }, true)
wrap.addEventListener('click', () => { console.log('wrap'); },true)
box .addEventListener('click', () => { console.log(box ); },true)

结果如下

JS事件流的实际应用

但是实际开发中经常会有多个容器的嵌套,就拿掘金官网来说

当我们点击文章标题或者其他文字部分的时候,我们是会直接打开这篇文章的,但是如果我们点击的是大拇指的按钮,我们只会给这篇文章点一个赞,并不会打开这篇文章。

而很明显的是,这个大拇哥很明显是属于这篇文章的容器内的,那为什么我们点击大拇哥不会打开文章呢?这就得靠stopPropagation了。stopPropagation是时间监听器自带的一个方法,其作用就是阻止默认事件的传播,例如当我们在box元素上调用stopPropagation函数时,事件流到达box之后就会被阻断,不会继续冒泡到wrap和app身上

js 复制代码
app.addEventListener('click', () => { console.log('app'); })
wrap.addEventListener('click', () => { console.log('wrap'); })
box .addEventListener('click', () => {
console.log('box');
stopPropagation()
})

既然都提到了stopPropagation,那就不得不多嘴提一下stopImmediatePropagation了。相信不用我说,光靠眼睛都能发现它比上面那个玩意多一个单词。而正所谓一寸长一寸强,stopPropagation能干的,它能干,stopPropagation不能干的,它也能干。那么具体点说,就是stopImmediatePropagation能够阻止同一dom元素上绑定多个相同的事件,完整的代码如下

js 复制代码
const app = document.getElementById('app')
        const wrap = document.getElementById('wrap')
        const box = document.getElementById('box')


        app.addEventListener('click', () => { console.log('app'); },true)//绑定,订阅,注册
        wrap.addEventListener('click', () => { console.log('wrap'); },true)
        //事件监听的第三个参数默认就是false,表示这个事件会在冒泡过程被触发,而true则是在捕获过程被触发
        box.addEventListener('click', (e) => {
            // 点击box之后只打印box而不打印其他,需要阻止冒泡的继续发生
            console.log('box');
            e.stopImmediatePropagation()
            // stopImmediatePropagation会阻止事件流的传递同时阻止同一个dom元素的相同事件发生
          
        },true)
        box.addEventListener('mouseenter', (e) => {
            // 这里监听的是mouseenter事件而不是click,所以不会被阻止
            console.log('mouseenter');

        })
        box.addEventListener('click', (e) => {
            // 这里监听的还是box的click事件,会被阻止
            console.log('box2');

        })

总结

JavaScript事件流描述了事件在DOM元素中传播和触发的顺序。它分为捕获阶段、目标阶段和冒泡阶段。在捕获阶段,事件从根节点向目标元素传播,然后在目标阶段触发,最后在冒泡阶段从目标元素向根节点传播。通过理解事件流,我们可以更好地控制事件处理程序的执行顺序,以及在需要时阻止事件的传播或默认行为的发生。

相关推荐
IT_陈寒1 分钟前
为什么我的Java Stream操作总是默默吃掉异常?
前端·人工智能·后端
志尊宝7 分钟前
Vue3 零基础每日笔记(017):事件处理进阶——$event、多事件与事件修饰符全家桶
javascript·vue.js·笔记
光影少年28 分钟前
react navite浏览器 Event Loop 和 Node Event Loop 的区别
前端·javascript·react native·react.js·前端框架
90后的晨仔33 分钟前
uni-app 安卓自有证书完全指南
前端
江湖十年36 分钟前
Go 虚荣域名详解:我用 go get -x 抓了个包,终于看清了 Go 工具链“对暗号”的全过程
后端·面试·go
CharlesYu0111 小时前
前端性能优化的第一性原理,是不断缩短“用户发起意图 → 获得可用结果”之间的时间
前端
平头哥技术团队11 小时前
Day 21 _ 页内锚点_给每段起个 id,目录写 href=_#id_,点一下页面就滚到那一段
前端·html·html5
子兮曰12 小时前
Bun v1.4.1 深度解析:从 Zig 到 Rust,一场 11 天、64 个 AI 代理的语言迁徙
前端·后端·bun
人民广场吃泡面12 小时前
什么是AI Agent?它又能给前端带来哪些效率提升?
前端·人工智能