谁能解释一下JavaScript中的事件委托,它是如何有用的?


当前回答

委托是一种技术,在这种技术中,一个对象向外部表达某种行为,但实际上将实现该行为的责任委托给一个关联对象。乍听起来,这与代理模式非常相似,但其目的却大不相同。委托是一种集中对象(方法)行为的抽象机制。

一般来说:使用委托来替代继承。继承是一个很好的策略,当父对象和子对象之间存在密切的关系时,但是,继承会非常紧密地结合对象。通常,委托是表达类之间关系的更灵活的方式。

这种模式也称为“代理链”。其他一些设计模式使用委托——状态模式、策略模式和访问者模式都依赖于它。

其他回答

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

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

委托是一种技术,在这种技术中,一个对象向外部表达某种行为,但实际上将实现该行为的责任委托给一个关联对象。乍听起来,这与代理模式非常相似,但其目的却大不相同。委托是一种集中对象(方法)行为的抽象机制。

一般来说:使用委托来替代继承。继承是一个很好的策略,当父对象和子对象之间存在密切的关系时,但是,继承会非常紧密地结合对象。通常,委托是表达类之间关系的更灵活的方式。

这种模式也称为“代理链”。其他一些设计模式使用委托——状态模式、策略模式和访问者模式都依赖于它。

这基本上就是如何关联元素。.click应用于当前DOM,而.on(使用委托)将继续对事件关联后添加到DOM的新元素有效。

哪一种更好,要看具体情况而定。

例子:

<ul id="todo">
   <li>Do 1</li>
   <li>Do 2</li>
   <li>Do 3</li>
   <li>Do 4</li>
</ul>

.Click事件:

$("li").click(function () {
   $(this).remove ();
});

事件内:

$("#todo").on("click", "li", function () {
   $(this).remove();
});

注意,我在.on中分离了选择器。我会解释为什么。

让我们假设,在这种联系之后,让我们做以下的事情:

$("#todo").append("<li>Do 5</li>");

这就是你会注意到区别的地方。

如果事件是通过.click关联的,任务5将不服从click事件,因此它将不会被删除。

如果它是通过.on关联的,选择器是分开的,它将服从。

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

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

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

下面是一个简单的例子(为了允许内联解释,它故意很冗长):处理容器表中任意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事件规范(现在已被取代,但图形仍然有效)中的图形非常好地显示了它:

并非所有事件都会冒泡,但大多数都会冒泡,包括点击。

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

这段代码被编写为详细地调用各个步骤,但在模糊的现代浏览器上(如果你使用polyfill,也可以在IE上),你可以使用nearest和contains来代替循环:

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");
}

生活例子:

// Handle the event on the container document.getElementById("container").addEventListener("click", function(event) { var target = event.target.closest("td"); if (target && this.contains(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>

closest checks the element you call it on to see if it matches the given CSS selector and, if it does, returns that same element; if not, it checks the parent element to see if it matches, and returns the parent if so; if not, it checks the parent's parent, etc. So it finds the "closest" element in the ancestor list that matches the selector. Since that might go past the container element, the code above uses contains to check that if a matching element was found, it's within the container — since by hooking the event on the container, you've indicated you only want to handle elements within that container.

回到我们的表格例子,这意味着如果你在一个表格单元格中有一个表格,它不会匹配包含表格的表格单元格:

// Handle the event on the container document.getElementById("container").addEventListener("click", function(event) { var target = event.target.closest("td"); if (target && this.contains(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; } <!-- The table wrapped around the #container table --> <table> <tbody> <tr> <td> <!-- This cell doesn't get matched, thanks to the `this.contains(target)` check --> <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> </td> <td> This is next to the container table </td> </tr> </tbody> </table>

事件委托利用了JavaScript事件中两个经常被忽视的特性:事件冒泡和目标元素。当一个事件在一个元素上被触发时,例如鼠标点击一个按钮,同样的事件也会在该元素的所有祖先上被触发。这个过程被称为事件冒泡;事件从初始元素冒泡到DOM树的顶部。

Imagine an HTML table with 10 columns and 100 rows in which you want something to happen when the user clicks on a table cell. For example, I once had to make each cell of a table of that size editable when clicked. Adding event handlers to each of the 1000 cells would be a major performance problem and, potentially, a source of browser-crashing memory leaks. Instead, using event delegation, you would add only one event handler to the table element, intercept the click event and determine which cell was clicked.