是什么

JS 事件委托(事件代理)是一种利用事件冒泡机制来优化事件处理的技术。

原理

在 JavaScript 的事件流中,事件会经历捕获阶段、目标阶段和冒泡阶段。当一个元素上的事件被触发时,该事件会从最具体的目标元素(即被点击、鼠标悬停等操作的元素)开始向上冒泡,依次传递到它的父元素、祖父元素等,直到到达文档的根节点。事件委托就是利用这个冒泡机制,将事件处理程序绑定到目标元素的父元素或更外层的祖先元素上,当子元素上的事件触发时,会冒泡到外层的祖先元素,从而在外层元素的事件处理程序中进行处理。

优点

应用场景

示例

  1. 首先获取了 id 为 parent-list 的 ul 元素,它是子元素 li 的父元素。
  2. 然后为 ul 元素添加了一个 click 事件监听器。当在 ul 元素内部的任何地方发生点击事件时,这个事件监听器都会被触发。
  3. 在事件处理函数中,通过 event.target 获取到了实际被点击的元素。然后使用 classList.contains 方法检查被点击的元素是否具有 item 类名,以此来判断是否是我们想要处理的子元素。如果是子元素,则打印出子元素的文本内容,表示该子元素被点击了。

这样做的好处是,无论后续动态添加多少个新的 li 元素到 ul 中,都不需要为每个新元素单独添加点击事件监听器,它们的点击事件都会自动冒泡到父元素 ul 上,并被父元素的事件监听器处理,大大提高了代码的效率和可维护性。

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>事件代理</title>
  <style>
   .item {
      border: 1px solid #ccc;
      padding: 10px;
      margin: 10px;
    }
  </style>
</head>

<body>
  <button onclick="addItem()">Add Item</button>
  <ul id="parent-list">
    <li class="item">Item 1</li>
    <li class="item">Item 2</li>
    <li class="item">Item 3</li>
  </ul>

  <script>
    // 获取父元素
    const parentList = document.getElementById('parent-list');

    // 为父元素添加点击事件监听器
    parentList.addEventListener('click', function (event) {
      // 获取点击的目标元素
      const target = event.target;

      // 判断目标元素是否是子元素(这里根据类名判断)
      if (target.classList.contains('item')) {
        // 执行相应的操作
        console.log('Clicked on item: ' + target.textContent);
      }
    });

    function addItem() {
      const oItem = document.createElement('li');
      oItem.classList.add('item');
      oItem.innerText = `Item ${parentList.children.length + 1}`;
      parentList.appendChild(oItem);
    }
  </script>
</body>

</html>

⭐️ 手写事件委托

// 原生JS
var ul = document.querySelector('ul');
function listen(element, eventType, targetElement, fn) {
    element.addEventListener(eventType, function(e) {
        // 先拿到当前事件的直接触发对象
        var curTarget = e.target;
        // 看它是不是使用者监听的目标对象类型
        // 一旦发现不是,就执行循环
        while(!curTarget.matches(targetElement)) {
            // 先看看当前对象是不是和父元素相同
            // 相同则把当前对象置为空,且不执行回调
            if (curTarget === element) {
                curTarget = null;
                break;
            }
            // 不相同则把当前对象设置成自己的父对象
            curTarget = curTarget.parentNode;
        }
        // 是,则先看当前对象有没有值,有值则执行回调函数
        curTarget && fn(e, curTarget);
    });
};

listen(ul, 'click', 'li', function(event, el) {
    console.log(event, el);
});

// jquery
$("ul").on("click", "li", function(e) {
  console.log($(e.target).html());
});
// 这个on事件是绑定在ul上面的,li是目标元素,
// on事件内部是通过e.target来判断点击元素是不是li的