JS事件机制的传播与事件委托详解

1,465 阅读4分钟

前言

在我们前端开发中,想操作DOM结构时,事件机制是我们离不开的东西。那事件机制的定义是什么呢?事件机制就是指浏览器在页面中发生某些特定的交互行为(点击,移动鼠标,键盘输入等)的时候触发的对应的事件,这时我们就可以用addEventListener方法监听触发的事件。

那什么又是事件委托呢?事件委托就是利用时间冒泡的机制,将事件监听器绑定到父组件上,而不绑定在子组件上,当事件被触发时,事件就会向上冒泡到父元素上,父元素通过判断事件的来源来给该子元素做出相应的操作。

写到这里,相信小伙伴们应该明白了什么是事件机制和事件委托。那下面我就来给小伙伴们介绍介绍事件机制的捕获阶段和冒泡阶段以及事件委托是怎么写的。

事件机制的捕获阶段和冒泡阶段

在JavaScript中,事件触发会经历两个阶段:捕获阶段和冒泡阶段。这两个阶段构成了事件流,决定了事件的传播和处理顺序。

事件机制的触发的是三个阶段:

  1. 捕获阶段(Capture Phase):
    • 事件从最外层的祖先元素逐级向下传播到目标元素。
    • 在捕获阶段,如果父元素上有addEventListener事件监听器,它会在目标元素触发之前被触发。
  2. 目标阶段(Target Phase):
    • 事件到达目标元素时触发。
    • 在目标阶段,触发绑定在目标元素的addEventListener事件监听器
  3. 冒泡阶段(Bubbing Phase):
    • 事件从目标元素开始向上传播到最外层的组件元素。
    • 在冒泡阶段,如果父元素上有addEventListener事件监听器,它会在目标元素触发之后被触发。

tips:并非所有事件都支持捕获阶段。一些常见的事件(如鼠标点击事件)只支持冒泡阶段。

addEventListener事件接受三个参数,第三个参数为可选参数,可以是一个布尔值或者一个对象。当该参数设置为布尔值时,true表示事件在捕获触发,false表示时间在冒泡阶段触发(默认)。当该参数为对象时,里面可配置的属性如下:

  • capture:一个布尔值,表示是否在捕获阶段触发事件监听器。
  • once:一个布尔值,表示事件监听器只会在触发一次后自动移除。
  • passive:一个布尔值,表示事件监听器不会调用 preventDefault()

下面我们通过一段代码来更深入的了解事件流传播顺序:

<body>
    <div id="app">
      <div id="title"></div>
    </div>
</body>
<script>
    let app = document.getElementById('app')
    let title = document.getElementById('title')
    app.addEventListener('click',(event) => {console.log('捕获阶段-父元素')},true) // 捕获 - 父元素
    app.addEventListener('click',(event) => {console.log('冒泡阶段-父元素')},false) // 冒泡  - 父元素
    title.addEventListener('click',(event) => {console.log('捕获阶段-子元素');},true) // 捕获 - 子元素
    title.addEventListener('click',(event) => {console.log('冒泡阶段-子元素');}) // 冒泡 - 子元素
</script>

当我们点击子元素时,该代码执行结果如下图,也就印证了事件机制的触发顺序是从外到内,再从内到外

image.png

阻止事件传播的方法

上述介绍了事件机制的三个阶段,那这时问题就来了,如果我想在某个阶段阻止事件传播该怎么办?接下来我再来和大家介绍事件机制上阻止事件传播的方法:

stopPropagation

stopPropagation 方法是事件机制上一个阻止冒泡的方法,当我们在子元素上调用stopPropagation 方法,事件就会停止冒泡,也就无法传播到更高层级的元素。当我把上述代码的子元素冒泡事件的代码改成这样:

 title.addEventListener(
     'click',
     (event) => {
        console.log('冒泡阶段-子元素')
        event.stopPropagation()  // 阻止冒泡
     },true) //// 冒泡 - 子元素

当我们点击子元素时,该代码执行结果如下图, 也就阻止了冒泡事件。

image.png

stopImmediatePropagation

既然有阻止冒泡事件传递的方法,那肯定有阻止捕获事件的方法吧,然而JS并没有阻止捕获事件的方法,有的是立即阻止事件进一步传播的方法--stopImmediatePropagation。也就说当我把上述代码的父元素捕获事件的代码改成这样:

app.addEventListener(
      'click',
      (event) => {
        console.log('捕获阶段-父元素');
        event.stopImmediatePropagation()
      },
      true
    )

当我们点击子元素时,该代码执行结果如下图, 直接阻止了事件往下传播。

image.png

事件委托

上面我们介绍了事件机制的传播,那接下来我们再来看一看借助事件机制传播是实现的事件委托是怎么样实现的?

首先我们来看一段实现事件委托的代码:

<body>
    <ul id ='ul'>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
</body>
<script>
    let ul = document.getElementById('ul')
    ul.addEventListener('click', (event) => {
      console.log(event.target.innerHTML);
    })
</script>

如上述代码,我们通过在父元素帮绑定一个事件,通过事件流的传播,我们就可以知道我们点的是哪个DOM结构,这样我们如果想要操作子元素就变得方便了。

image.png

那既然介绍完了看事件委托的优点:

  • 减少事件监听器的数量,提高性能。
  • 简化动态添加或删除元素时的事件管理。
  • 可以处理大量子元素的事件,而不用为每个子元素都添加事件监听器。事件委托的实现,最后我们再来看

总结

写到这里,我相信小伙伴们也就明白了事件机制的传播和事件委托了,也能灵活的运用addEventListener了吧。