什么是 DOM 事件委托?

IT技术 javascript event-handling dom-events event-delegation
2020-12-26 00:41:34

谁能解释一下 JavaScript 中的事件委托,它有什么用?

6个回答

DOM 事件委托是一种通过事件“冒泡”(又名事件传播)的魔法,通过单个公共父级而不是每个子级响应 ui 事件的机制。

当在元素上触发事件时,会发生以下情况

事件被分派到其目标, EventTarget并且在那里找到的任何事件侦听器都会被触发。 然后,冒泡事件将触发通过向上跟踪EventTarget父链找到的任何其他事件侦听器,检查在每个连续 EventTarget 上注册的任何事件侦听器。这种向上传播将持续到并包括Document

事件冒泡为浏览器中的事件委托提供了基础。现在,您可以将事件处理程序绑定到单个父元素,只要该事件发生在其任何子节点(以及它们的任何子节点)上,就会执行该处理程序这是事件委托。下面是它在实践中的一个例子:

<ul onclick="alert(event.type + '!')">
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
</ul>

在该示例中,如果您单击任何子<li>节点,您将看到 警告"click!",即使<li>您单击的没有绑定单击处理程序如果我们绑定onclick="..."到每个<li>你会得到相同的效果。

那么有什么好处呢?

想象一下,您现在需要<li>通过 DOM 操作将新项目动态添加到上述列表中:

var newLi = document.createElement('li');
newLi.innerHTML = 'Four';
myUL.appendChild(newLi);

如果不使用事件委托,您将不得不将"onclick"事件处理程序“重新绑定”到新<li>元素,以便它的行为与其兄弟元素相同。使用事件委托,您无需执行任何操作。只需将新添加<li>到列表中即可。

这对于将事件处理程序绑定到许多元素的 Web 应用程序来说非常棒,其中新元素在 DOM 中动态创建和/或删除。通过事件委托,事件绑定的数量可以通过将它们移动到一个公共父元素来大幅减少,并且动态创建新元素的代码可以与绑定其事件处理程序的逻辑分离。

事件委托的另一个好处是事件侦听器使用的总内存占用减少了(因为事件绑定的数量减少了)。对于经常卸载的小页面(即用户经常导航到不同页面),它可能没有太大区别。但对于长期应用程序来说,它可能很重要。当从 DOM 中删除的元素仍然要求内存(即它们泄漏)时,有一些非常难以追踪的情况,并且这种泄漏的内存通常与事件绑定相关联。使用事件委托,您可以自由地销毁子元素而不会忘记“解除绑定”它们的事件侦听器(因为侦听器位于祖先上)。然后可以包含这些类型的内存泄漏(如果不消除,有时很难做到。IE 我在看着你)。

下面是一些更好的事件委托的具体代码示例:

真的很棒的答案。感谢您用相关事实解释事件委托。谢谢!
2021-02-07 00:41:34
@Crescent Fresh 那么如果事件从未到达它,它如何应用于子节点?
2021-02-08 00:41:34
@Aetos: > 如果这个节点的父节点是有问题的事件目标,它为什么可以到达节点的子元素? 它不能,据我所知。事件在目标的父级完成阶段 1(捕获),在目标本身上进入阶段 2(目标),然后从目标的父级开始进入阶段 3(冒泡)。它无处到达目标的孩子。
2021-02-16 00:41:34
您好,感谢您的精彩解释。不过,我仍然对某些细节感到困惑:我理解 DOM 树事件流的方式(如3.1. 事件调度和 DOM 事件流中所示),事件对象传播直到到达目标元素然后冒泡。如果这个节点的父节点是有问题的事件目标,它为什么可以到达节点的子元素?例如,事件如何传播到<li>应该停止的时间<ul>如果我的问题仍然不清楚或需要一个单独的线程,我很乐意提供帮助。
2021-02-17 00:41:34
我在没有 javascript 库的情况下打开您的第三个链接事件委托时被禁止访问,并为您的最后一个链接 +1
2021-03-08 00:41:34

事件委托允许您避免向特定节点添加事件侦听器;相反,事件侦听器被添加到一个父级。该事件侦听器分析冒泡事件以查找子元素的匹配项。

JavaScript 示例:

假设我们有一个带有多个子元素的父 UL 元素:

<ul id="parent-list">
  <li id="post-1">Item 1</li>
  <li id="post-2">Item 2</li>
  <li id="post-3">Item 3</li>
  <li id="post-4">Item 4</li>
  <li id="post-5">Item 5</li>
  <li id="post-6">Item 6</li>
</ul>

我们也可以说当点击每个子元素时需要发生一些事情。您可以为每个单独的 LI 元素添加一个单独的事件侦听器,但是如果 LI 元素频繁地从列表中添加和删除怎么办?添加和删​​除事件侦听器将是一场噩梦,尤其是当添加和删除代码位于您的应用程序中的不同位置时。更好的解决方案是向父 UL 元素添加事件侦听器。但是如果将事件侦听器添加到父级,如何知道单击了哪个元素?

简单:当事件冒泡到 UL 元素时,您检查事件对象的目标属性以获得对实际单击节点的引用。这是一个非常基本的 JavaScript 片段,它说明了事件委托:

// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click", function(e) {
  // e.target is the clicked element!
  // If it was a list item
  if(e.target && e.target.nodeName == "LI") {
    // List item found!  Output the ID!
    console.log("List item ", e.target.id.replace("post-"), " was clicked!");
   }
});

首先向父元素添加一个单击事件侦听器。当事件侦听器被触发时,检查事件元素以确保它是要响应的元素类型。如果是 LI 元素,boom:我们有我们需要的东西!如果它不是我们想要的元素,则可以忽略该事件。这个例子非常简单——UL 和 LI 是一个直接的比较。让我们尝试一些更困难的事情。让我们有一个有很多孩子的父 DIV,但我们只关心一个带有 classA CSS 类的 A 标签:

// Get the parent DIV, add click listener...
document.getElementById("myDiv").addEventListener("click",function(e) {
// e.target was the clicked element
  if(e.target && e.target.nodeName == "A") {
    // Get the CSS classes
    var classes = e.target.className.split(" ");
    // Search for the CSS class!
    if(classes) {
        // For every CSS class the element has...
        for(var x = 0; x < classes.length; x++) {
            // If it has the CSS class we want...
            if(classes[x] == "classA") {
                // Bingo!
                console.log("Anchor element clicked!");
                // Now do something here....
            }
        }
    }
  }
});

http://davidwalsh.name/event-delegate

建议调整:在最后一个示例中使用 e.classList.contains() 代替:developer.mozilla.org/en-US/docs/Web/API/Element/classList
2021-02-24 00:41:34

dom 事件委托与计算机科学的定义有所不同。

它指的是处理来自许多元素(如表格单元格)、来自父对象(如表格)的冒泡事件。它可以使代码更简单,尤其是在添加或删除元素时,并节省一些内存。

委托概念

如果一个父元素中有很多元素,并且您想处理其中的事件 - 不要将处理程序绑定到每个元素。相反,将单个处理程序绑定到其父级,并从 event.target 获取子级。该站点提供了有关如何实现事件委托的有用信息。 http://javascript.info/tutorial/event-delegation

事件委托正在处理使用容器元素上的事件处理程序冒泡的事件,但仅当事件发生在容器内与给定条件匹配的元素上时才激活事件处理程序的行为。这可以简化处理容器内元素的事件。

例如,假设您要处理对大表格中任何表格单元格的单击。可以编写一个循环来将点击处理程序连接到每个单元格……或者您可以在表格上连接一个点击处理程序并使用事件委托来仅针对表格单元格(而不是表格标题或表格中的空格)触发它在单元格周围排行等)。

当您要从容器中添加和删除元素时,它也很有用,因为您不必担心在这些元素上添加和删除事件处理程序;只需在容器上挂钩事件并在事件冒泡时处理该事件。

这是一个简单的例子(它是故意冗长的以允许内联解释):处理对td容器表中任何元素的单击

// Handle the event on the container
document.getElementById("container").addEventListener("click", function(event) {
    // Find out if the event targeted or bubbled through a `td` en route to this container element
    var element = event.target;
    var target;
    while (element && !target) {
        if (element.matches("td")) {
            // Found a `td` within the container!
            target = element;
        } else {
            // Not found
            if (element === this) {
                // We've reached the container, stop
                element = null;
            } else {
                // Go to the next parent in the ancestry
                element = element.parentNode;
            }
        }
    }
    if (target) {
        console.log("You clicked a td: " + target.textContent);
    } else {
        console.log("That wasn't a td in the container table");
    }
});
table {
    border-collapse: collapse;
    border: 1px solid #ddd;
}
th, td {
    padding: 4px;
    border: 1px solid #ddd;
    font-weight: normal;
}
th.rowheader {
    text-align: left;
}
td {
    cursor: pointer;
}
<table id="container">
    <thead>
        <tr>
            <th>Language</th>
            <th>1</th>
            <th>2</th>
            <th>3</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th class="rowheader">English</th>
            <td>one</td>
            <td>two</td>
            <td>three</td>
        </tr>
        <tr>
            <th class="rowheader">Español</th>
            <td>uno</td>
            <td>dos</td>
            <td>tres</td>
        </tr>
        <tr>
            <th class="rowheader">Italiano</th>
            <td>uno</td>
            <td>due</td>
            <td>tre</td>
        </tr>
    </tbody>
</table>

在进入细节之前,让我们提醒自己 DOM 事件是如何工作的。

DOM 事件从文档分派到目标元素(捕获阶段),然后从目标元素冒泡回文档(冒泡阶段)。DOM3 事件规范中的这个图形(现已被取代,但图形仍然有效)显示得非常好:

在此处输入图片说明

并非所有事件都会冒泡,但大多数都会冒泡,包括click.

上面代码示例中的注释描述了它是如何工作的。matches检查元素是否与 CSS 选择器匹配,当然,如果您不想使用 CSS 选择器,您可以通过其他方式检查某些元素是否与您的条件匹配。

编写该代码是为了详细地调用各个步骤,但是在模糊的现代浏览器上(如果使用 polyfill,也可以在 IE 上),您可以使用closestandcontains代替循环:

var target = event.target.closest("td");
    console.log("You clicked a td: " + target.textContent);
} else {
    console.log("That wasn't a td in the container table");
}

现场示例:

closest检查您调用它的元素以查看它是否与给定的 CSS 选择器匹配,如果匹配,则返回相同的元素;如果不匹配,则检查父元素是否匹配,如果匹配则返回父元素;如果没有,它会检查父级的父级等。因此它会在祖先列表中找到与选择器匹配的“最近”元素。由于这可能会经过容器元素,因此上面的代码contains用于检查是否找到了匹配的元素,它是否在容器内——因为通过将事件挂接到容器上,您已经表明您只想处理该容器内的元素.

回到我们的表格示例,这意味着如果您在表格单元格中有一个表格,它将与包含该表格的表格单元格不匹配: