什么是DOM事件委托?

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

140143 次浏览

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

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

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

c#中的委托类似于C或c++中的函数指针。使用委托允许程序员在委托对象中封装对方法的引用。然后可以将委托对象传递给可以调用引用方法的代码,而不必在编译时知道将调用哪个方法。

查看这个链接——> http://www.akadia.com/services/dotnet_delegates_and_events.html

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

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

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

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

事件被分派到它的目标 EventTarget和任何事件监听器 发现有触发。冒泡 事件将触发任何 找到的其他事件侦听器 跟随EventTarget的父类 chain 向上,检查任何事件 侦听器注册在 连续EventTarget。这个向上 延续到和 包括Document.

.

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

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

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

那么好处是什么呢?

假设你现在需要通过DOM操作向上面的列表动态添加新的<li>项:

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

没有使用事件委托,你必须将"onclick"事件处理程序“重新绑定”到新的<li>元素,以使它与它的兄弟元素的作用相同。事件委托你不需要做任何事情。只需将新的<li>添加到列表中,就完成了。

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

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

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

委托的概念

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

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

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元素时,检查事件对象的target属性以获得对实际单击的节点的引用。下面是一个非常基本的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元素,那么我们就得到了我们需要的东西!如果它不是我们想要的元素,则可以忽略事件。这个例子非常简单——UL和LI是一个直接的比较。让我们试试更难的。让我们有一个父DIV和许多子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

这就是元素的关联方式。.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事件规范中的这个图形(现在已被取代,但该图形仍然有效)非常好地显示了它:

enter image description here

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

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

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

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检查调用它的元素是否匹配给定的CSS选择器,如果匹配,则返回相同的元素;如果不匹配,则检查父元素是否匹配,如果匹配则返回父元素;如果不是,则检查父节点的父节点,等等。因此,它会在祖先列表中找到与选择器匹配的“最近”元素。由于这可能超出了容器元素,所以上面的代码使用contains来检查是否找到了匹配的元素,它在容器 —因为通过在容器上钩子事件,你已经表明你只想处理容器的元素

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

// 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树的顶部。

想象一个有10列100行的HTML表格,当用户单击表格单元格时,您希望发生一些事情。例如,我曾经必须在单击时将相同大小的表的每个单元格设置为可编辑的。将事件处理程序添加到1000个单元格中的每一个都将是一个主要的性能问题,并且可能会导致浏览器崩溃的内存泄漏。相反,使用事件委托,只需向表元素添加一个事件处理程序,拦截单击事件并确定单击了哪个单元格。

事件的代表团

将事件监听器附加到父元素,当子元素上发生事件时触发该监听器。

事件传播

当一个事件通过DOM从子元素移动到父元素时,这被称为事件传播,因为事件在DOM中传播或移动。

在本例中,按钮的事件(onclick)被传递给父段。

$(document).ready(function() {


$(".spoiler span").hide();


/* add event onclick on parent (.spoiler) and delegate its event to child (button) */
$(".spoiler").on( "click", "button", function() {
    

$(".spoiler button").hide();
    

$(".spoiler span").show();
    

} );


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>


<p class="spoiler">
<span>Hello World</span>
<button>Click Me</button>
</p>

< a href = " https://codepen。io/ antlove /pen/abojvLL" rel="nofollow noreferrer"> code depen .

要理解事件委托,首先我们需要知道为什么以及什么时候需要或想要事件委托。

可能有很多情况,但让我们讨论事件委托的两个大用例。 1. 第一种情况是当一个元素有很多感兴趣的子元素时。在本例中,我们不向所有这些子元素添加事件处理程序,而是将其添加到父元素,然后确定在哪个子元素上触发事件。< / p >

2.事件委托的第二个用例是当我们想要将事件处理程序附加到加载页面时还不在DOM中的元素时。当然,这是因为我们不能将事件处理程序添加到不在页面上的东西,所以在不赞成的情况下,我们正在编写代码。

假设在加载页面时,DOM中有一个包含0、10或100个条目的列表,并且还有更多条目等待添加到列表中。因此,没有办法为未来的元素附加事件处理程序,或者这些元素还没有添加到DOM中,而且可能有很多项,因此每个项都附加一个事件处理程序是没有用的。

事件的代表团

为了讨论事件委托,我们需要讨论的第一个概念是事件冒泡。

< >强事件冒泡: 事件冒泡意味着当一个事件在某些DOM元素上被触发或触发时,例如通过点击下面图片上的按钮,那么完全相同的事件也会在所有父元素上被触发

enter image description here

事件首先在按钮上触发,但随后它也会在所有父元素上触发,因此它也会在段落到section的主元素上触发,实际上在DOM树中一直到HTML元素,也就是根元素。所以我们说事件在DOM树中冒泡,这就是为什么它被称为冒泡。

1 2 3 4 < / p >

第一次触发事件的元素被称为目标元素,所以导致事件发生的元素被称为目标元素。在我们上面的例子中,它当然是被点击的按钮。重要的部分是这个目标元素作为一个属性存储在事件对象中,这意味着事件也将触发的所有父元素将知道事件的目标元素,因此事件是在哪里第一次触发的。

这就引出了事件委托,因为如果事件在DOM树中冒泡,并且如果我们知道事件在哪里被触发,那么我们可以简单地将事件处理程序附加到父元素并等待事件冒泡,然后我们可以对目标元素做任何我们打算做的事情。这种技术称为事件委托。在本例中,我们可以简单地添加事件处理程序

事件委托不是在我们感兴趣的原始元素上设置事件处理程序而是将它附加到父元素上,基本上,在那里捕获事件,因为它弹出。然后,我们可以使用目标元素属性对感兴趣的元素进行操作。

< >强的例子: 现在让我们假设我们的页面中有两个列表项,在以编程的方式在列表中添加项目后,我们想要从其中删除一个或多个项目。使用事件委托技术,我们可以很容易地达到我们的目的

<div class="body">
<div class="top">


</div>
<div class="bottom">
<div class="other">
<!-- other bottom elements -->
</div>
<div class="container clearfix">
<div class="income">
<h2 class="icome__title">Income</h2>
<div class="income__list">
<!-- list items -->
</div>
</div>
<div class="expenses">
<h2 class="expenses__title">Expenses</h2>
<div class="expenses__list">
<!-- list items -->
</div>
</div>
</div>
</div>
</div>

在列表中添加项目:

const DOMstrings={
type:{
income:'inc',
expense:'exp'
},
incomeContainer:'.income__list',
expenseContainer:'.expenses__list',
container:'.container'
}




var addListItem = function(obj, type){
//create html string with the place holder
var html, element;
if(type===DOMstrings.type.income){
element = DOMstrings.incomeContainer
html = `<div class="item clearfix" id="inc-${obj.id}">
<div class="item__description">${obj.descripiton}</div>
<div class="right clearfix">
<div class="item__value">${obj.value}</div>
<div class="item__delete">
<button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>
</div>
</div>
</div>`
}else if (type ===DOMstrings.type.expense){
element=DOMstrings.expenseContainer;
html = ` <div class="item clearfix" id="exp-${obj.id}">
<div class="item__description">${obj.descripiton}</div>
<div class="right clearfix">
<div class="item__value">${obj.value}</div>
<div class="item__percentage">21%</div>
<div class="item__delete">
<button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>
</div>
</div>
</div>`
}
var htmlObject = document.createElement('div');
htmlObject.innerHTML=html;
document.querySelector(element).insertAdjacentElement('beforeend', htmlObject);
}

删除条目:

var ctrlDeleteItem = function(event){
// var itemId = event.target.parentNode.parentNode.parentNode.parentNode.id;
var parent = event.target.parentNode;
var splitId, type, ID;
while(parent.id===""){
parent = parent.parentNode
}
if(parent.id){
splitId = parent.id.split('-');
type = splitId[0];
ID=parseInt(splitId[1]);
}


deleteItem(type, ID);
deleteListItem(parent.id);
}


var deleteItem = function(type, id){
var ids, index;
ids = data.allItems[type].map(function(current){
return current.id;
});
index = ids.indexOf(id);
if(index>-1){
data.allItems[type].splice(index,1);
}
}


var deleteListItem = function(selectorID){
var element = document.getElementById(selectorID);
element.parentNode.removeChild(element);
}