js事件流流程如下:
- 在window上往事件触发处传播,遇到注册的"捕获事件"会触发
- 到达事件触发处
- 从事件触发处往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.stopImmediatePropagation
,event.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...