JavaScript事件机制之捕获、冒泡与事件委托详解

 更新时间:2025年11月26日 10:47:03   作者:秋氘渔  
JavaScript事件机制是指在JavaScript中处理用户交互或其他事件的机制,这篇文章主要介绍了JavaScript事件机制之捕获、冒泡与事件委托的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

1.什么是JavaScript的事件机制?

JavaScript的事件机制一共分为三个部分,分别是事件捕获,事件冒泡事件委托。

(1)事件捕获:指的是事件从根节点(document节点)向目标事件传播的过程。

(2)事件冒泡:指的是当捕获到目标事件之后从目标事件开始向上传播(传播至Document节点)的过程。

(3)事件委托:指的是利用事件冒泡机制,在对应子元素的父元素节点上绑定事件处理器,避免对多个子元素分别绑定事件监听器。这样做可以显著减少内存占用和事件处理器的数量,提升页面性能,尤其是在子元素数量很多或动态生成的场景下,事件委托能降低浏览器的事件管理开销,提高响应效率和代码维护性。

想要掌握好JavaScript中的事件机制,首先掌握好事件是如何被捕获的(事件捕获阶段),捕获到了事件是怎么触发的(事件执行阶段),事件触发之后会怎么样(事件冒泡阶段)。理解这些之后就知道如何利用事件冒泡来进行事件委托了。

2.DOM树

在讲述事件机制之前,首先掌握一个概念,什么是DOM树。理解了DOM树可以让我们更好的理解事件的触发和冒泡过程。

什么是DOM树?

  • DOM(文档对象模型,Document Object Model)是浏览器解析HTML后生成的一个树状结构。
  • 每个HTML标签、文本节点、属性都会被表示成DOM树上的一个节点。
  • 通过DOM树,JavaScript可以访问、修改网页内容和结构。

案例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="container">
      <button id="btn">点击我</button>
    </div>

</body>
</html>

DOM树为:

Document
 └── html
      └── body
           └── div#container
                └── button#btn (文本节点: "点击我")

3.事件触发流程

假设我们在上面HTML中给按钮和它的父元素div都绑定点击事件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="container">
      <button id="btn">点击我</button>
    </div>

    <script>
      const container = document.getElementById('container');
      const btn = document.getElementById('btn');

      container.addEventListener('click', () => {
        console.log('容器被点击');
      });

      btn.addEventListener('click', () => {
        console.log('按钮被点击');
      });
    </script>


</body>
</html>

事件触发流程:

  1. 当用户点击按钮时,浏览器根据DOM树找到事件目标(button)。
  2. 事件先从document开始捕获,经过html、body、div,最后到button(捕获阶段)。明事件捕获默认是不启用的,需要在addEventListener第三个参数设置为true才会执行捕获阶段的监听。否则事件监听默认只在目标阶段和冒泡阶段触发。
  3. 事件在button上触发(目标阶段)
  4. 事件从button向上冒泡,经过div、body、html、document(冒泡阶段)
  5. 绑定在button和container上的事件都会被触发,输出:按钮被点击 容器被点击。

从上述案例我们可以看出,当我们在给一个事件绑定了监听器之后,如果该监听器被触发了,浏览器首先需要找到目标元素(捕获阶段),找到目标元素之后浏览器会触发当前元素绑定的事件监听器的事件(目标阶段),当执行完当前的事件之后会继续沿着DOM树向上触发(冒泡阶段),由于<div id="container">是该按钮的父元素并且也绑定了事件监听,所以也会触发,直至传递到根节点。

4. 如何利用事件冒泡机制(事件委托)

假设你有一个列表,里面有很多条目,每条目都有一个“删除”按钮:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul id="list">
      <li>项目1 <button class="delete-btn">删除</button></li>
      <li>项目2 <button class="delete-btn">删除</button></li>
      <li>项目3 <button class="delete-btn">删除</button></li>
      <!-- 可能还有更多项目 -->
    </ul>


</body>
</html>

(1)传统做法(不使用事件委托):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul id="list">
      <li>项目1 <button class="delete-btn">删除</button></li>
      <li>项目2 <button class="delete-btn">删除</button></li>
      <li>项目3 <button class="delete-btn">删除</button></li>
      <!-- 可能还有更多项目 -->
    </ul>
    <script>
        const deleteButtons = document.querySelectorAll('.delete-btn');
        deleteButtons.forEach(btn => {
          btn.addEventListener('click', function() {
            const li = this.parentElement;
            li.remove();
            console.log('删除了一个项目');
          });
        });

    </script>


</body>
</html>

我们发现,如果想要点击按钮就删除对应元素,我们可以给每一个按钮都绑定事件监听器来达到效果。这样对于少部分的事件其实并没有什么影响,但是如果事件很多的情况下,绑定大量事件监听器会消耗内存和性能。而且如果后来还需要动态添加新的项目,则需要额外绑定事件。

(2)利用事件冒泡机制(事件委托)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul id="list">
      <li>项目1 <button class="delete-btn">删除</button></li>
      <li>项目2 <button class="delete-btn">删除</button></li>
      <li>项目3 <button class="delete-btn">删除</button></li>
      <!-- 可能还有更多项目 -->
    </ul>
    <script>
        const list = document.getElementById('list');

        list.addEventListener('click', function(event) {
          // 利用事件冒泡,判断点击目标是否是删除按钮
          if (event.target && event.target.classList.contains('delete-btn')) {
            const li = event.target.parentElement;
            li.remove();
            console.log('删除了一个项目(事件委托)');
          }
        });

    </script>


</body>
</html>

从上述案例我们可以发现,我们只绑定了一个事件监听(ul),但是当子元素(li)触发点击事件之后,我们并没有对子元素绑定事件处理,但是也能够进行删除操作,这是因为子元素的事件会沿着DOM树向上传播,当传播到ul节点的时候触发了ul的事件处理机制。所以进行了删除。

5.如何阻止事件冒泡

当我们希望事件只在某个特定元素上响应,而不影响其父元素或祖先元素时,可以使用 event.stopPropagation() 来阻止事件冒泡。

此外,如果希望不仅阻止事件冒泡,还阻止当前元素上后续的同类型事件监听器执行,可以使用 event.stopImmediatePropagation()

区别:

  • event.stopPropagation():阻止事件继续冒泡到父元素,但当前元素上绑定的其他同类型事件监听器仍会执行。
  • event.stopImmediatePropagation():阻止事件冒泡,并且阻止当前元素上后续同类型事件监听器的执行。

案例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>阻止事件冒泡案例</title>
</head>
<body>
  <div id="outer" style="padding:20px; border:1px solid #ccc;">
    外层容器(点击会触发)
    <button id="inner">点击我</button>
  </div>

  <script>
    const outer = document.getElementById('outer');
    const inner = document.getElementById('inner');

    outer.addEventListener('click', () => {
      console.log('【外层容器】点击事件触发');
    });

    // 监听器1:使用 stopPropagation()
    inner.addEventListener('click', (event) => {
      event.stopPropagation();  // 阻止事件冒泡,父元素的点击事件不会触发
      console.log('【按钮监听器1】点击事件触发,事件冒泡被阻止,父元素不会收到事件');
    });

    // 监听器2:同一元素上的另一个监听器
    inner.addEventListener('click', () => {
      console.log('【按钮监听器2】点击事件触发,仍然执行');
    });

    /*
    // 如果改为使用 stopImmediatePropagation(),效果如下:
    inner.addEventListener('click', (event) => {
      event.stopImmediatePropagation();  // 阻止事件冒泡,且阻止后续监听器执行
      console.log('【按钮监听器1】点击事件触发,事件冒泡和后续监听器执行被阻止,父元素不会收到事件');
    });

    inner.addEventListener('click', () => {
      console.log('【按钮监听器2】不会执行');
    });
    */
  </script>
</body>
</html>

使用 stopPropagation() 时:

点击按钮会输出:

【按钮监听器1】点击事件触发,事件冒泡被阻止,父元素不会收到事件
【按钮监听器2】点击事件触发,仍然执行

使用 stopImmediatePropagation() 时:

点击按钮只会输出:

【按钮监听器1】点击事件触发,事件冒泡和后续监听器执行被阻止,父元素不会收到事件

6.总结

  • JavaScript事件机制包括三个主要阶段:

    1. 捕获阶段:事件从document节点向目标元素逐层传递,监听器在此阶段触发需设置capturetrue
    2. 目标阶段:事件到达目标元素,触发绑定在目标元素上的事件监听器。
    3. 冒泡阶段:事件从目标元素向上传播到document,触发沿途元素绑定的冒泡监听器。
  • 事件冒泡机制允许我们通过事件委托,只在父元素上绑定事件监听器,利用事件冒泡捕获子元素事件,极大优化性能和代码维护。

  • 在需要阻止事件传播时,可使用event.stopPropagation()阻止冒泡,避免父元素响应事件;使用event.stopImmediatePropagation()则进一步阻止当前元素上后续同类型监听器执行。

  • 事件监听器默认在目标阶段和冒泡阶段触发,捕获阶段监听需要显式开启

到此这篇关于JavaScript事件机制之捕获、冒泡与事件委托的文章就介绍到这了,更多相关JS事件捕获、冒泡与事件委托内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论