深入理解 JavaScript 事件监听:从机制到实践

64 阅读5分钟

一、引言

在 JavaScript 开发中,事件监听是实现交互功能的核心技术之一。无论是响应用户的点击、键盘输入,还是页面的加载完成等,都离不开事件监听。本文将深入探讨 JavaScript 中事件监听的相关知识,包括事件如何发生、事件机制以及实际的应用示例,帮助你全面掌握这一重要技能。

二、事件的发生过程

  1. 事件与 DOM 元素的紧密联系:事件通常发生在 DOM(文档对象模型)元素上。我们所看到的网页是通过 DOM 结构以平面形式绘制出来的。当用户与页面进行交互(如点击、滚动等)时,相应的事件就会在特定的 DOM 元素上触发。

  2. 事件传播的三个阶段

    • 捕获阶段(capture) :事件从 document 对象开始,沿着 DOM 树向下传播,逐步缩小范围,就像水从树的顶端开始向下流淌。例如,如果在某个 parent 节点上添加了点击事件监听,在捕获阶段,该事件会从 document 依次经过各级祖先节点到达 parent 节点。
    • 目标阶段(event.target) :当事件到达实际触发事件的目标元素(child 元素)时,就进入了目标阶段。此时,事件就在这个具体的目标元素上进行处理。
    • 冒泡阶段(bubble) :事件处理完目标阶段后,会从目标元素开始,沿着 DOM 树向上传播,就像气泡从水底往上冒。在这个阶段,决定了事件处理函数的执行顺序,通常先执行子元素的事件监听,再执行父元素的事件监听。

三、JavaScript 事件机制

  1. 异步特性:JavaScript 事件是异步的。这意味着我们首先需要注册事件监听,然后当相应事件触发时,才会执行注册的回调函数。这种异步机制使得 JavaScript 能够在不阻塞主线程的情况下处理各种事件,保证页面的流畅性。

  2. 事件注册的不同标准

    • DOM0 级事件:这是早期的事件注册方式,直接在 DOM 节点上定义事件处理函数,例如 element.onclick = function() {}。这种方式虽然简单,但存在一些问题,比如不利于模块化开发,一个元素只能绑定一个相同类型的事件处理函数,如果重复绑定会覆盖之前的设置,所以不推荐使用。

    • DOM2 级事件:使用 addEventListener 方法进行事件注册,这是目前广泛使用的方式。addEventListener(event_type, callback, useCapture) 方法接收三个参数:

      • event_type:表示事件类型,是一个字符串,如 'click'(点击事件)、'keydown'(键盘按下事件)等。
      • callback:即事件处理函数,当事件触发时会执行该函数。
      • useCapture:是一个布尔值,用于指定是否在捕获阶段处理事件。默认值为 false,表示在冒泡阶段处理事件;如果设置为 true,则在捕获阶段处理事件。
  3. 事件监听的注意事项

    • 事件监听的对象:事件监听必须绑定在单个 DOM 元素上,不能直接在元素集合上进行。例如,不能对 document.querySelectorAll('li') 返回的集合直接添加事件监听,而需要遍历集合,为每个 li 元素单独添加监听。
    • 内存开销:事件监听会带来一定的内存开销,因为每次添加事件监听时,浏览器都需要为其分配内存空间来存储相关的信息。如果在页面中大量使用事件监听而不进行合理的管理,可能会导致内存泄漏,影响页面性能。
    • event.target 的作用event.target 表示事件实际触发的元素。在事件处理函数中,通过 event.target 可以获取到具体触发事件的 DOM 元素,方便进行针对性的操作。

四、代码示例分析

  1. 事件传播阶段演示

html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS 事件机制</title>
  <style>
    #parent {
      width: 200px;
      height: 200px;
      background-color: red;
    }

    #child {
      width: 100px;
      height: 100px;
      background-color: blue;
    }
  </style>
</head>

<body onclick="alert('橘子')">
  <div id="parent">
    <div id="child"></div>
  </div>
  <script>
    document.getElementById('parent').addEventListener('click', function () {
      console.log('parent click');
    }, true)
    document.getElementById('child').addEventListener('click', function () {
      console.log('clild click');
    }, true)
    document.getElementById('parent').addEventListener('click', function () {
      console.log('parent click');
    }, false)
    document.getElementById('child').addEventListener('click', function () {
      console.log('clild click');
    }, false)
  </script>
</body>

</html>

在这个示例中,我们为 parent 和 child 元素分别在捕获阶段(useCapture = true)和冒泡阶段(useCapture = false)添加了点击事件监听。当点击 child 元素时,由于捕获阶段先于冒泡阶段,所以首先会输出 parent click(捕获阶段 parent 的监听),接着输出 clild click(捕获阶段 child 的监听),然后在冒泡阶段,又会依次输出 clild click(冒泡阶段 child 的监听)和 parent click(冒泡阶段 parent 的监听)。

  1. 事件委托示例

html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>事件委托</title>
</head>

<body>
  <ul id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
  <script>
    const lis = document.querySelectorAll('#list li')
    console.log(lis);
    for (let i = 0; i < lis.length; i++) {
      lis[i].addEventListener('click', function () {
        console.log(this.innerHTML);
      })
    }
  </script>
</body>

</html>

在这个示例中,我们通过遍历获取到 ul 下的所有 li 元素,并为每个 li 元素添加了点击事件监听。当点击某个 li 元素时,事件处理函数会输出该 li 元素的内部 HTML 内容。这展示了如何为多个相似元素添加事件监听的常见做法。

五、总结

JavaScript 的事件监听机制是构建交互式网页的关键。了解事件的发生过程、事件机制以及如何正确使用事件监听方法,对于开发者来说至关重要。通过合理运用事件监听,我们可以实现丰富的用户交互功能,提升用户体验。同时,要注意事件监听的性能问题,避免不必要的内存开销。希望本文能帮助你深入理解并熟练运用 JavaScript 事件监听技术。