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

js事件流流程如下:

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

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

js事件流

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

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

<div id="app">
    <div id="wrapper">
        <div id="box">

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

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

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

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

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事件流第三步,冒泡事件。冒泡事件是什么?又为何直接到第三步了?下面一一带你认识下

捕获事件

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

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

冒泡事件

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

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


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

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

addEventListener的第三个参数

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

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

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

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

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

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

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

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

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

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

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

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

event.stopPropagation和event.stopImmediatePropagation

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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…

原文链接:https://juejin.cn/post/7320288231195754546 作者:Dolphin_海豚

(0)
上一篇 2024年1月6日 上午10:16
下一篇 2024年1月6日 上午10:26

相关推荐

发表回复

登录后才能评论