js的事件冒泡、捕获、委托

事件不仅存在js中,也存在在其他语言中,js事件背后的主要思想是能够在特定事件发生时运行代码。

先普及一个概念,什么是事件处理程序?

事件处理程序就像一个特殊的通用遥控器,可以执行某些操作,例如更改电视频道、提高空调温度以及更改家中照明的状态。但他们需要专业人员来处理附在其上的特殊按钮。

上面的类比解释了 JavaScript 中事件处理的概念。遥控器代表您正在使用的应用程序。应用程序上类似 HTML 元素的按钮是附加到遥控器上的特殊按钮。而处理特殊按钮的专业人士就是JavaScript。

JavaScript 中的事件处理使应用程序和网页具有响应性和交互性。他们通过定义和管理事件并在这些事件发生时执行特定操作来实现这一点。

事件处理程序是专门定义用于响应 JavaScript 中的特定事件的任何函数或方法。它还负责在特定事件发生时执行代码。

  1. 一个事件由不同的操作(点击、鼠标移动、事件间隔);
  2. 每当事件发生时,js就会创建一个事件对象。该对象具有属性和方法,并提供事件的详细信息;
  3. 可以使用js中的事件处理程序设置一个在事件发生时执行的函数;
  4. 每当触发事件时,都会自动创建事件对象。因此该对象会作为参数传递给事件处理函数;
  <button id="button"> click me </button>
  <script>
    var btn = document.getElementById('button')
    btn.addEventListener('click', (e) => {
      console.log('event type', e.type)
      console.log('target Element', e.target)
    })
  </script>
关于事件冒泡、事件捕获、事件委托

点名中心:事件委托是一种技术,冒泡是事件本身所做的事情,捕获是对不冒泡的事件使用事件委托的一种方法。

DOM事件流(event flow)存在三个阶段:捕获阶段、目标阶段、冒泡阶段
在这里插入图片描述

捕获阶段:
当嵌套在各个元素中的元素被单击时发生。在单击到达最终的目的地之前,必须触发其每个父元素的单击事件。此阶段从DOM树的顶部向下渗透到目标元素。
目标阶段:
当完成捕获阶段后立即开始的阶段,这个阶段基本上就是捕获阶段结束和冒泡阶段的开始。
目标元素是事件最初发生的元素。例如:如果网页上有提交表单,则表单元素就是提交事件的目标元素。如果网页上的按钮上有事件处理程序,则该按钮是事件处理程序的目标元素。
按钮元素上注册的事件处理程序被视为目标元素。
冒泡阶段:
冒泡阶段和捕获阶段恰好相反。在此阶段,事件将目标元素通过其父元素(祖先)向上冒泡到全局窗口对象。默认情况下,您添加到所有事件都addEventListener处于冒泡阶段。
在这里插入图片描述

什么是事件冒泡
事件开始是触发.child它的元素。然后,它向上冒泡到其每个父元素,直到到达该html元素。
以表单字段为例,事件将向上冒泡到父表单,然后是表单所在的任何容器或 div,然后是body,然后是html元素,然后是document,然后是window。
当这些父元素冒泡时,任何监听器都会被触发。
事件冒泡的工作原理
DOM在目标元素处理完事件后,事件开始在层次结构中向上冒泡。然后事件从目标元素传播到文档的根。它根据所有的祖先元素在冒泡阶段的嵌套方式来遍历它们。
在事件处理程序的执行阶段,所有在祖先元素上注册的事件处理程序都会被执行。这样可以轻松捕获DOM层次结构各个阶段的事件。
可以使用stopPropagation()事件对对象上的方法来停止此操作。当您的目标是防止触发祖先元素上捕获到的事件处理程序时,它非常有用。
通过冒泡,可以分层方式处理事件。通过在父元素或容器上设置事件处理程序,您可以处理来自多个子元素上的事件。
事件冒泡可以轻松地实现事件委托。
在这里插入图片描述

例子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Practice</title>
  </head>
  <body>
    <h1>Bubbling and Capturing phase</h1>

    <div>
      <button class="child">click me</button>
    </div>

    <script>
      const parent = document.querySelector("div");
      const child = document.querySelector(".child");

      parent.addEventListener("click", function () {
        console.log("clicked parent");
      });

      child.addEventListener("click", function () {
        console.log("clicked child");
      });
    </script>
  </body>
</html>

代码说明:上面是一段 HTML 和 JavaScript 代码。

在 body 元素内部,我们有h1、div和button元素。div 是按钮元素的父元素。我们为按钮元素指定了 child 的类名。

在 JavaScript 部分,我们为父元素和子元素创建了变量。然后我们向父元素和子元素添加事件侦听器。

因此,当单击按钮时,首先调用被单击的子级。这意味着父元素中的函数在子元素中的函数之后执行。

什么是事件捕获

单击嵌套元素时会发生事件捕获。其父元素的点击事件必须在嵌套元素的点击之前触发。此阶段从树的顶部向下滴落DOM到目标元素。
addEventListener只有将的第三个参数设置为Boolean true值(默认值为false)后,事件捕获才会发生。
对于不冒泡的事件,请将其设置true为无论如何都捕获事件。
每当第三个参数addEventListener设置为true时,事件处理程序就会在捕获阶段自动触发。这样,当层次结构中的嵌套元素上发生事件时,将首先执行附加到祖先元素的事件处理程序DOM。这与默认情况不同,默认情况下事件处理程序是在冒泡阶段触发的。
在这里插入图片描述

重点代码示例:此示例其实已经涵盖了这篇文章的中心
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <!-- 事件捕获 -->
  <ul id="list">
    <li id="listLi">1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
  </ul>
  <script>
    const list = document.getElementById('list');

    /**
     * 将点击事件都委托在ul标签上
     */
    list.addEventListener('click', function(e) {
      // e.stopPropagation()
      console.log(e.target, 'click list')
    }, false)
    /**
     * 先执行子元素的click,再执行父元素的click
     */
    listLi.addEventListener('click', function(e) {
      /**
       * 设置这个方法,就不会再执行父元素的click事件,因为阻止了事件冒泡,所以要想让父元素上获取到,就需要在父元素的监听的click事件上的第三个参数设置为true,
       * 注意:第三个参数设置为true了,那么会先执行父元素的click事件,然后才会执行子元素的点击事件。
       * 那么既然li#listLi已经委托给ul的标签上执行click事件了,然后我在li#listLi上再次绑定了click事件,我设置了stopPropogation,让父标签不进行数据捕获,那么我如何
      */
      // e.stopPropagation();
      
      /**
      * 纵观此示例的代码,li#listLi上绑定了多个click事件,使用stopImmediatePropagation这个方法,就是为了解决li#listLi上绑定了多个click事件的多次执行问题,但是不影响ul标签的委托事件执行。它仅此与一个标签上绑定的事件以及停止冒泡等功能
      */
      e.stopImmediatePropagation()
      console.log(e, 'click list li')
    })
    listLi.addEventListener('click', function(e) {
      /**
       * 设置这个方法,就不会再执行父元素的click事件,因为阻止了事件冒泡,所以要想让父元素上获取到,就需要在父元素的监听的click事件上的第三个参数设置为true,
       * 注意:第三个参数设置为true了,那么会先执行父元素的click事件,然后才会执行子元素的点击事件。
       * 那么既然li#listLi已经委托给ul的标签上执行click事件了,然后我在li#listLi上再次绑定了click事件,我设置了stopPropogation,让父标签不进行数据捕获,那么我如何
      */
      // e.stopPropagation();
      console.log(e, 'click list li')
    })
    listLi.addEventListener('click', function(e) {
      /**
       * 设置这个方法,就不会再执行父元素的click事件,因为阻止了事件冒泡,所以要想让父元素上获取到,就需要在父元素的监听的click事件上的第三个参数设置为true,
       * 注意:第三个参数设置为true了,那么会先执行父元素的click事件,然后才会执行子元素的点击事件。
       * 那么既然li#listLi已经委托给ul的标签上执行click事件了,然后我在li#listLi上再次绑定了click事件,我设置了stopPropogation,让父标签不进行数据捕获,那么我如何
      */
      // e.stopPropagation();
      console.log(e, 'click list li')
    })
  </script>
</body>
</html>

stopPropagation:
该stopPropagation()方法防止调用同一事件的传播。
传播意味着向上冒泡到父元素或向下捕获到子元素。

什么是事件委托

事件委托是一种侦听事件的技术,您可以将父元素委托为其中发生的所有事件的侦听器。
使用监听addEventListener点击document作为示例,但它可以是页面上的任何元素。
JavaScript 中的事件委托可以更轻松地管理和处理多个子元素上的事件。它利用DOM(文档对象模型)冒泡事件。这意味着在祖先元素上设置事件侦听器可以让您有效地处理事件。与在触发事件的单个元素上设置事件侦听器不同。回想一下,在冒泡阶段,子元素上的事件会上升到其父元素。
单个绑定的监听点击事件,示例:这种方法比较笨,可以说这种使用办事效率差

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Practice</title>
  </head>
  <body>
    <ul>
      <li>Orange</li>
      <li>Banana</li>
      <li>Potato</li>
      <li>Apple</li>
    </ul>

    <script>
      const li = document.querySelectorAll("li");

      li.forEach(function (list) {
        list.addEventListener("click", function () {
          alert("Yes, I click");
        });
      });
    </script>
  </body>
</html>

在上面的代码中,事件侦听器被添加到每个listItems. 然后,每当点击它们时,就会弹出警报。传统上,这不是坏代码,但它违背了事件委托的目的。在下面的代码中,您将看到事件委托的重要性。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Practice</title>
  </head>
  <body>
    <h1>Bubbling and Capturing phase</h1>

    <ul>
      <li>Kamal</li>
      <li>Lawal</li>
      <li>Olaide</li>
      <li>Ayinde</li>
    </ul>

    <script>
      const ul = document.querySelector("ul");

      ul.addEventListener("click", function () {
        event.target.classList.toggle("highlight");
      });
    </script>
  </body>
</html>

与初始代码不同,listItems 的父级ul将在此处处理该事件。所以父级会将我们想要实现的逻辑委托给子级。回想一下listItems树上的气泡DOM。
但在某些情况下,几乎不可能委托给子元素。例如,如果子元素本身具有嵌套子元素,则这是不可能的。在这种情况下,不可能委托给子元素。
下面的代码示例向您展示了为什么无法委托给子元素:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Practice</title>
  </head>
  <body>
    <h1>Bubbling and Capturing phase</h1>

    <ul>
      <li>
        <h2>Orange</h2>
        <p>Kamal</p>
      </li>
      <li>
        <h2>Apple</h2>
        <p>Lawal</p>
      </li>
      <li>
        <h2>Banana</h2>
        <p>Olaide</p>
      </li>
    </ul>

    <script>
      const ul = document.querySelector("ul");

      ul.addEventListener("click", function () {
        event.target.classList.toggle("highlight");
      });
    </script>
  </body>
</html>

上面的代码不会产生预期的输出。这是因为在我们的代码中,我们使用event.target.
在 JavaScript 中,是被点击的event.target实际元素。DOM在本例中,三种可能的结果是li、h2和p。下面的代码展示了这个问题的解决方案:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Practice</title>
  </head>
  <body>
    <h1>Bubbling and Capturing phase</h1>

    <ul>
      <li>
        <h2>Orange</h2>
        <p>Kamal</p>
      </li>
      <li>
        <h2>Apple</h2>
        <p>Lawal</p>
      </li>
      <li>
        <h2>Banana</h2>
        <p>Olaide</p>
      </li>
    </ul>

    <script>
      const ul = document.querySelector("ul");

      ul.addEventListener("click", function () {
        event.target.closest("li").classList.toggle("highlight");
      });
    </script>
  </body>
</html>

为了得到选择listItem的预期结果,我们将DOM遍历方法与event.target.
该closest方法可用于DOM对象。closest在祖先树中向上遍历。您可以通过 ID、标签或类名使用任何 CSS 选择器选择最接近的元素。一件有趣的事情是,其中closest包括您调用它的元素,在本例中为li.

e.preventDefault()

用于执行操作时停止浏览器的默认行为。
例如:

  1. 单击复选框/无线电 输入选择/取消选择复选框
  2. 单击输入/文本区域字段会聚焦输入并将光标置于输入元素中

event.stopPropagation()

此方法用于防止浏览器中事件流的捕获/冒泡阶段中定义的事件传播。

event.stopImmediatePropagation()

如果我们在单个 HTML 元素上有多次单击侦听器怎么办?
例如:代码示例的运行

<script> 
    $("div").click(function(event) { 
      event.stopImmediatePropagation(); 
      alert('首次点击触发'); 
    }); 

    $("div").click(function(event) { 
      // 该函数不会被执行
      alert('第二次点击触发'); 
    }); 
</script>
<div>点我</div>

单击 div 元素将:

  • 防止事件冒泡到父元素
  • 阻止执行附加到该元素的任何其他事件侦听器
    stopImmediatePropagation= stopPropagation+(删除了其他事件侦听器

参考:

带你理解DOM事件流
freecodecamp 事件冒泡和捕获
gomakethings 事件委托、冒泡和捕获的区别

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值