JavaScript DOM事件流之捕获与冒泡

DOM事件流——捕获与冒泡

网页是由一个一个元素组成的,正如我们肉眼所见,网页上的元素存在包含关系,简单的点击又怎么确定到底谁来触发响应呢?想象一下,在纸上画了两个大小不同的同心圆,然后用手指指向它里面的圆,那么你能说了指向的圆没有包含外面的圆吗?显然不是的,为了解决这个问题,出现了事件流。

DOM事件流三阶段

当用户与HTML页面交互(如点击按钮、移动鼠标等)时,就会产生事件,而DOM结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素节点与根结点之间的路径传播,路径所经过的结点都会收到该事件,这个传播过程可称为DOM事件流。DOM事件流主要分为三个阶段:捕获阶段、目标阶段、冒泡阶段。

1、捕获阶段Capturing 捕获是由网景公司最先提出的,事件从文档的根节点开始,‌向下传播至目标元素。‌在这个过程中,‌事件会经过目标元素的父元素、‌祖父元素等,‌但通常不会在这个阶段触发事件监听器,‌除非特别指定。这可以用于在事件传播到特定子元素之前,先进行一些全局性的处理或检查。

‌2、目标阶段Target 当事件到达目标元素时,‌事件会被触发,‌并执行绑定在该元素上的事件监听器。监听器就会按照它们被添加到元素上的顺序被调用,不管是捕获还是冒泡事件,只会按添加的顺序执行(注意⚠️:在最新的浏览器版本中如果同时存在默认和冒泡,都会先执行捕获!!!)

3、冒泡阶段Bubbling‌:冒泡是IE最早提出的,事件从目标元素开始,‌向上冒泡至根节点。‌在这个过程中,‌事件同样会经过目标元素的父元素、‌祖父元素等,‌且可以在这些元素上触发事件监听器。‌这通常用于实现事件的委托(Event Delegation),即在一个父元素上监听子元素的事件,从而减少事件监听器的数量,提高性能。在实际开发中我们使用跟多的也是冒泡,很少用到捕获。

在这里插入图片描述

代码示例,单击白色inner盒子,执行结果如下:

<!DOCTYPE html>
<html>
  <head>
    <title>DOM事件流</title>
  </head>
  <body>
    <div class="outer">
      <div class="inner"></div>
    </div>
  </body>
  <script>
    // 获取DOM元素
    let outer = document.querySelector('.outer')
    let inner = document.querySelector('.inner')
    // 绑定事件
    outer.addEventListener('click', () => console.log('outer冒泡阶段'), false) // 5
    outer.addEventListener('click', () => console.log('outer捕获阶段'), true) // 1
    inner.addEventListener('click', () => console.log('inner冒泡阶段'), false) // 4
    inner.addEventListener('click', () => console.log('inner捕获阶段方法1'), true) // 2
    inner.addEventListener('click', () => console.log('inner捕获阶段方法2'), true) //3
  </script>
  <style>
    .outer {
      display: flex;
      align-items: center;
      width: 300px;
      height: 300px;
      background-color: pink;
    }

    .inner {
      margin: 0 auto;
      width: 200px;
      height: 200px;
      background-color: white;
    }
  </style>
</html>

在这里插入图片描述

可见,事件的整体顺序是:祖先元素捕获 -> 目标元素捕获 -> 目标元素冒泡 -> 祖先元素冒泡(⚠️老版本浏览器目标元素可能按绑定顺序执行)

无法冒泡的事件

基本上只有onloadunloadfocusblursubmitchange事件是不支持冒泡的

阻止冒泡

为什么要阻止冒泡

1、提高性能和效率:如果多个元素(如父元素和子元素)都绑定了相同或相似的事件处理函数,并且这些函数都执行了相似的操作,那么当事件发生时,这些函数都会被调用,从而导致重复的计算或处理,浪费资源。通过阻止事件冒泡,可以避免这种不必要的重复处理,提高代码的性能和效率。

2、控制事件传播范围:有时候,我们可能只希望事件在特定的元素上触发,而不希望它继续传播到父元素或祖先元素上。通过阻止事件冒泡,我们可以精确地控制事件的传播范围,确保事件只在目标元素上触发。

3、防止事件的冲突和干扰:在复杂的前端应用中,可能存在多个嵌套的元素,它们都有自己的事件处理逻辑。如果事件冒泡到了父元素或祖先元素,可能会触发其他元素上的事件处理函数,导致事件的冲突和干扰。

如何阻止冒泡

1、stopPropagation()标准方法:利用事件对象里面的stopPropagation()方法

    outer.addEventListener('click', () => console.log('outer冒泡阶段'), false)
    outer.addEventListener('click', () => console.log('outer捕获阶段'), true)
    inner.addEventListener('click', () => console.log('inner冒泡阶段'), false)
    inner.addEventListener(
      'click',
      () => {
        console.log('inner捕获阶段方法1')
        event.stopPropagation()
      },
      true
    )

执行结果如下,在捕获阶段使用stopPropagation()冒泡都被阻止了

在这里插入图片描述

2、cancelBubble兼容性方法:虽然stopPropagation()已被大多数浏览器支持,但IE6-8需要利用事件对象的cancelBubble属性阻止冒泡,即设置:window.event.cancelBubble = true;

3、在事件处理函数中返回false:这个方法很多地方都有提到,但实践起来并不奏效,首先他限定事件处理函数是通过HTML属性(如onclick)或者某些特定的JavaScript库(如jQuery)以特定方式注册的,其次不同浏览器处理方式也不一样,最新的浏览器中大多数已经无法奏效了

4、设置CSS属性:虽然CSS本身不直接提供阻止事件冒泡的功能,但有时候通过改变元素的显示方式(如display: none;)或可交互性(如pointer-events: none;),可以间接影响事件的传播,这个方法具有局限性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值