面试官:请问js事件触发过程是怎样的

js事件流流程如下:

  1. 在window上往事件触发处传播,遇到注册的"捕获事件"会触发
  2. 到达事件触发处
  3. 从事件触发处往window上传播,遇到注册的"冒泡事件"会触发

js事件流默认都在冒泡的过程触发

js事件流

js事件流描述的就是js事件传播的一个顺序,上面的答案就是对应的三个阶段:捕获阶段目标阶段冒泡阶段。如何理解这三个阶段?下面我用一个小demo展示下

我给三个div盒子,层层嵌套 app> wrapper> box

bash 复制代码
<div id="app">
    <div id="wrapper">
        <div id="box">

        </div>
    </div>
</div>

再给点样式,app最大,wrapper其次,box最小,效果如下

我再给这三个盒子都分别绑定一个点击事件,从大到小绑定,并且每个点击事件都会打印自己的id,比如app容器绑定点击事件如下

javascript 复制代码
let app = document.getElementById("app");

app.addEventListener('click', (e) => { 
    console.log('app') 
})

我们给一个元素绑定一个事件,也可以说是注册,或者订阅一个事件

我们现在在app容器内点击,就一定会打印app,在wrapper内点击就一定会打印wrapper,在box内点击就一定会打印box,由于box被wrapper和app包含,点击box,就三个打印都会触发,那么这个触发的顺序是什么样的呢?这就是我们要讨论的话题,js事件触发的顺序就是js事件流

我们先看下他会如何打印,是从里往外还是从外往里?

浏览器告诉我们是从里往外的,从里往外就是对应着js事件流第三步,冒泡事件。冒泡事件是什么?又为何直接到第三步了?下面一一带你认识下

捕获事件

我们点击最里面的容器的时候,会顺带点击到中间的容器以及最外面的容器,js是这样的,事件传播顺序一定是从外向内,最外层是window,然后是html,然后是body,然后就是app,wrapper,box,很好理解,就是从外向内

捕获的含义其实就是父容器包裹一个子容器,捕获的过程碰到的事件就是捕获事件。

冒泡事件

当我们到达了事件触发处的时候,从事件触发处往window上传播。这就是从内向外。冒泡也非常的形象,从里往外,水里的泡泡因为压强的原因,深处的泡泡上浮的过程就是越来越大。刚好对应着这里的div盒子,从里向外,从小到大。

冒泡过程中碰到的事件我们就称之为冒泡事件


js事件流是先从外向里走一遍,在默认情况下,这个被称之为捕获的过程,是不会去触发事件的,到达了目标阶段后,开始往外冒泡,这个过程碰到的事件就是会触发的,这才有了刚才的打印顺序。

既然都说默认了,肯定有方法去更改这个js事件流,如何实现在捕获阶段触发呢?这个时候就要谈到addEventListener的第三个参数了

addEventListener的第三个参数

我们看看上面绑定事件时的addEventListener

javascript 复制代码
app.addEventListener('click', (e) => { 
    console.log('app') 
})

第一个参数就是事件名,第二个参数是回调函数,第三个参数可选,他是个布尔值,控制该事件是在捕获过程触发的还是在冒泡过程触发的,默认为false,所以false就代表着冒泡触发,true就代表让该事件在捕获过程被触发

所以我现在给容器最大的app加上第三个参数为true,那么app这个容器的事件触发将会被改为捕获阶段触发

javascript 复制代码
app.addEventListener('click', (e) => {
    console.log('app') 
}, true)

最终点击最小的容器,触发顺序为,捕获阶段先触发最大的app,然后到达了最小的容器后开始触发默认的冒泡事件,先是最小的box,然后是第二大的wrapper

现实中还有另外一个很常见的情形,就是我点击子容器,并不希望触发父容器。就拿掘金为栗,我可以在首页给一个文章点赞,而不会进入文章。

这个效果的实现我们就需要看下事件event原型中的一个方法:event.stopPropagation了,打印下看看

javascript 复制代码
app.addEventListener('click', (e) => { 
    console.log(e)
}, true)

event.stopPropagation和event.stopImmediatePropagation

Propagation就是传播的意思,所以stopPropagation就是阻止传播,并且冒泡和捕获阶段都可以进行阻止

要实现只触发子容器的点击事件很简单,我们只需要保证大家都是默认的冒泡事件,然后到了事件触发处,也就是最小的容器,开始冒泡给他阻止掉就可以,所以我们给最小的容器加个阻止传播就可以了

javascript 复制代码
app.addEventListener('click', (e) => {
    console.log('app') 
})

wrapper.addEventListener('click', () => {
    console.log('wrapper') 
})

box.addEventListener('click', (e) => { 
    console.log('box')
    e.stopPropagation()
})

我们给所有容器都改成捕获阶段触发,也就是给第三个参数为true,并且给最大的容器加上阻止传播,也可以实现仅打印最大的容器,这是为了证明可以阻止捕获传播,大家可以自行去试,这里就不作演示了。

现在再聊下event.stopImmediatePropagationevent.stopPropagation能实现的效果,他也能实现,区别在于它可以阻止同一个dom结构绑定的多个相同事件,不同事件的不行

刚才讲的效果你可以自己换成immediate再试一遍,效果是一样的。

我们现在只给最小的容器绑定多个事件

javascript 复制代码
box.addEventListener('click', (e) => { 
    console.log('box')
})

box.addEventListener('click', (e) => { 
    console.log('box2')
})

因为代码从上到下的执行缘故,他会打印box,box2

我们现在给box加上一个immediate阻止

javascript 复制代码
box.addEventListener('click', (e) => { 
    console.log('box')
    e.stopImmediatePropagation()
})

box.addEventListener('click', (e) => { 
    console.log('box2')
})

他就会阻止后面的相同事件,仅打印box

应用场景:项目是多个人开发的,别人也可以对该dom结构绑定点击事件,你可以给自己添加一个阻止事件,这样就不会引起冲突

当然,相同dom结构绑定不同的事件,是无法阻止的

javascript 复制代码
box.addEventListener('click', (e) => { 
    console.log('box')
    e.stopImmediatePropagation()
})

box.addEventListener('mouseleave', (e) => { 
    console.log('box2')
})

这里我点击box后,再鼠标移除box依旧是可以触发鼠标移出事件的。因此这个方法只能阻止同一dom绑定的多个相同事件

最后

以上这些知识点就是js非常基础的事件流,整个流程是先捕获后冒泡,触发过程默认情况是冒泡,也就是像泡泡一样,从里到外,想要改变成为捕获传播就需要动用addEventListener的第三个参数,将其设置成true就是捕获事件,想要阻止事件传播就要调用stopPropagation或者stopImmediatePropagation方法

另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请"点赞+评论+收藏"一键三连,感谢支持!

本次学习代码已上传至本人GitHub学习仓库:github.com/DolphinFeng...

相关推荐
zqx_721 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己37 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
NiNg_1_2342 小时前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript
读心悦2 小时前
如何在 Axios 中封装事件中心EventEmitter
javascript·http
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
神之王楠2 小时前
如何通过js加载css和html
javascript·css·html