有人能否解释一下JavaScript中的事件委托是什么以及它的作用是什么?
有人能否解释一下JavaScript中的事件委托是什么以及它的作用是什么?
DOM事件委托是一种通过单个公共父级而不是每个子级响应UI事件的机制,通过事件“冒泡”(也称为事件传播)实现。
当元素上触发事件时,会发生以下情况:
事件被分派到其目标
EventTarget
,并触发任何在那里找到的事件侦听器。冒泡事件将继续跟随EventTarget
的父级链向上,检查每个连续的EventTarget上注册的任何事件侦听器。这种向上的传播将持续到包括Document
在内。
事件冒泡为浏览器中的事件委托提供了基础。现在,您可以将事件处理程序绑定到单个父元素,并且无论事件发生在其任何子节点上(以及它们的任何子节点),该处理程序都将被执行。 这就是事件委托。 以下是实际示例:
<ul onclick="alert(event.type + '!')">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
我举个例子,如果你点击任何一个子节点<li>
,你会看到一个弹窗显示"click!"
,即使没有绑定到你点击的<li>
上。如果我们给每个<li>
绑定onclick="..."
,你也会得到同样的效果。
那么好处是什么?
现在想象一下,你需要通过DOM操作动态添加新的<li>
项到上面的列表中:
var newLi = document.createElement('li');
newLi.innerHTML = 'Four';
myUL.appendChild(newLi);
对于绑定到许多元素的事件处理程序的Web应用程序,其中在DOM中动态创建和/或删除新元素,这绝对是一个好消息。使用事件委托,可以通过将它们移动到共同的父元素来大大减少事件绑定的数量,并且可以将动态创建新元素的代码与绑定其事件处理程序的逻辑解耦。
事件委托的另一个好处是事件监听器使用的总内存占用量会降低(因为事件绑定的数量会降低)。对于经常卸载的小页面(即用户经常导航到不同的页面),可能没有太大区别。但对于长时间运行的应用程序,这可能是重要的。有一些非常难以跟踪的情况,当从DOM中删除元素时,它们仍然占用内存(即泄漏),并且通常这种泄漏的内存与事件绑定有关。使用事件委托,您可以自由地销毁子元素,而不必担心忘记“解除绑定”其事件侦听器(因为侦听器在祖先上)。这些类型的内存泄漏可以得到控制(如果不是消除,有时很难做到。IE我正在看着你)。
以下是事件委托的一些更好的具体代码示例:
focus
和blur
事件(它们不会冒泡)<ul>
停止时,事件如何传播到<li>
?如果我的问题还不清楚或需要单独的讨论,请告诉我。 - Imad事件委托允许您避免将事件监听器添加到特定节点;相反,事件监听器添加到一个父节点。该事件监听器分析冒泡事件以在子元素上找到匹配项。
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元素,那么太棒了,我们得到了所需的内容!如果它不是我们想要的元素,则可以忽略该事件。这个例子非常简单--对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....
}
}
}
}
});
DOM事件委托与计算机科学定义不同。
它指的是从许多元素(如表格单元格)处理冒泡事件,而不是从父对象(如表格)中处理。这可以使代码更简单,特别是在添加或删除元素时,并且节省一些内存。
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>
并非所有事件都会冒泡,但大多数事件会冒泡,包括 click
事件。
上面代码示例中的注释描述了它的工作原理。matches
检查元素是否与 CSS 选择器匹配,但如果您不想使用 CSS 选择器,当然也可以以其他方式检查是否符合您的条件。
该代码被编写为详细调用各个步骤,但在近代浏览器(如果使用 polyfill,则也适用于 IE)上,您可以使用 closest
和 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
检查调用它的元素是否与给定的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>
例子: 现在假设我们的页面上有两个列表项,在通过编程方式添加项目后,我们想要从它们中删除一个或多个项目。使用事件委托技术,我们可以轻松地实现我们的目的。
<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);
}
如果一个父元素内有多个子元素,你想对它们进行事件处理 - 不要给每个元素绑定处理程序。 相反,将单个处理程序绑定到它们的父元素,并从event.target获取子元素。 本站提供有关如何实现事件委托的有用信息。 http://javascript.info/tutorial/event-delegation
委托是一种技术,其中对象向外部表达某些行为,但实际上将实现该行为的责任委托给一个相关联的对象。这听起来与代理模式非常相似,但其目的却完全不同。委托是一种抽象机制,可集中对象(方法)行为。
通俗地说:使用委托作为继承的替代方案。当父子对象之间存在紧密关系时,继承是一种很好的策略,但继承会使对象之间耦合得非常紧密。通常,委托是表达类之间关系的更灵活方式。
该模式也称为“代理链”。其他几个设计模式使用委托-状态、策略和访问者模式都依赖于它。
这基本上是关于如何将关联与元素进行匹配。 .click
适用于当前DOM,而使用委托的.on
将继续对在事件关联后添加到DOM中的新元素有效。
哪个更好,我会说这取决于具体情况。
例如:
<ul id="todo">
<li>Do 1</li>
<li>Do 2</li>
<li>Do 3</li>
<li>Do 4</li>
</ul>
点击事件:
$("li").click(function () {
$(this).remove ();
});
事件 .on:
$("#todo").on("click", "li", function () {
$(this).remove();
});
$("#todo").append("<li>Do 5</li>");
这就是你会注意到差异的地方。
如果事件是通过 .click 进行关联的,任务5将不遵守点击事件,因此它将不会被移除。
如果它是通过 .on 与选择器分离进行关联的,它将遵守。
将事件监听器附加到父元素上,当子元素上发生事件时,该监听器会触发。
事件传播当事件从子元素经过 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>
{{链接1:Codepen}}