Xen*_*non 186 javascript event-handling event-delegation dom-events
任何人都可以用JavaScript解释事件委托,它有什么用?
Cre*_*esh 300
DOM事件委托是一种通过事件"冒泡"(又称事件传播)的魔力,通过单个共同父母而不是每个孩子来响应ui事件的机制.
在元素上触发事件时,会发生以下情况:
事件被分派到其目标,
EventTarget
并且发现触发的任何事件侦听器. 然后,冒泡事件将触发通过向上跟随EventTarget
父链发现的任何其他事件侦听器,检查在每个连续的EventTarget上注册的任何事件侦听器.这种向上传播将持续到并包括.Document
事件冒泡为浏览器中的事件委派提供了基础.现在,您可以将事件处理程序绑定到单个父元素,并且只要事件发生在任何子节点(以及它们的任何子节点)上,该处理程序就会执行.这是事件委托.这是实践中的一个例子:
<ul onclick="alert(event.type + '!')">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
Run Code Online (Sandbox Code Playgroud)
通过该示例,如果您单击任何子<li>
节点"click!"
,即使没有单击处理程序绑定到<li>
您,也会看到警报.如果我们绑定onclick="..."
每个<li>
你会得到相同的效果.
那么有什么好处呢?
想象一下,您现在需要<li>
通过DOM操作动态地将新项添加到上面的列表中:
var newLi = document.createElement('li');
newLi.innerHTML = 'Four';
myUL.appendChild(newLi);
Run Code Online (Sandbox Code Playgroud)
在不使用事件委托的情况下,您必须将"onclick"
事件处理程序"重新绑定" 到新<li>
元素,以使其与其兄弟节点的行为方式相同.通过事件委派,您无需执行任何操作.只需将新内容添加<li>
到列表中即可完成.
对于具有绑定到许多元素的事件处理程序的Web应用程序来说,这绝对是太棒了,在DOM中动态创建和/或删除新元素.通过事件委托,可以通过将事件绑定移动到公共父元素来大幅减少事件绑定的数量,并且动态创建新元素的代码可以与绑定其事件处理程序的逻辑分离.
事件委托的另一个好处是事件侦听器使用的总内存占用量下降(因为事件绑定的数量减少).对于经常卸载的小页面(即用户经常导航到不同的页面),它可能没什么区别.但对于长期存在的应用程序来说,它可能很重要.当从DOM中删除的元素仍然声称存储器(即它们泄漏)时,存在一些非常难以跟踪的情况,并且这种泄漏的存储器通常与事件绑定相关联.通过事件委派,您可以自由地销毁子元素而不会忘记"解除绑定"他们的事件监听器(因为监听器位于祖先上).然后可以包含这些类型的内存泄漏(如果没有消除,有时候很难做到.IE我在看着你).
以下是一些更好的事件委派的具体代码示例:
focus
和blur
事件(不泡沫)Osa*_*tta 28
事件委派允许您避免向特定节点添加事件侦听器; 相反,事件监听器被添加到一个父级.该事件监听器分析冒泡事件以查找子元素的匹配.
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>
Run Code Online (Sandbox Code Playgroud)
我们还要说,点击每个子元素时需要发生一些事情.您可以为每个单独的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!");
}
});
Run Code Online (Sandbox Code Playgroud)
首先将click事件侦听器添加到父元素.触发事件侦听器时,请检查事件元素以确保它是要响应的元素类型.如果它是一个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....
}
}
}
}
});
Run Code Online (Sandbox Code Playgroud)
http://davidwalsh.name/event-delegate
要理解事件委托,首先我们需要知道为什么以及何时真正需要或想要事件委托。
可能有很多情况,但让我们讨论事件委托的两个主要用例。1. 第一种情况是当我们有一个元素有很多我们感兴趣的子元素时。在这种情况下,我们不是向所有这些子元素添加事件处理程序,而是简单地将其添加到父元素,然后确定事件在哪个子元素上触发。
2.事件委托的第二个用例是,当我们加载页面时,我们希望将事件处理程序附加到尚未在 DOM 中的元素。当然,这是因为我们无法将事件处理程序添加到不在我们页面上的内容,因此在我们正在编码的情况下弃用。
假设加载页面时,DOM 中有一个包含 0、10 或 100 个项目的列表,并且还有更多项目等待添加到列表中。因此,无法为未来的元素附加事件处理程序,或者这些元素尚未添加到 DOM 中,而且可能有很多项目,因此为每个元素附加一个事件处理程序是没有用的。其中。
活动委托
好吧,为了讨论事件委托,我们实际上需要讨论的第一个概念是事件冒泡。
事件冒泡: 事件冒泡意味着当某个 DOM 元素上触发或触发事件时,例如通过单击下图中的按钮,所有父元素上也会触发完全相同的事件。
该事件首先在按钮上触发,但随后它也会一次在所有父元素上触发,因此它也会在段落到主元素的部分上触发,实际上在 DOM 树中一直向上触发直到作为根的 HTML 元素。所以我们说事件在 DOM 树内部冒泡,这就是它被称为冒泡的原因。
目标元素:实际首先触发事件的元素称为目标元素,因此导致事件发生的元素称为目标元素。在我们上面的示例中,当然是被单击的按钮。重要的是,该目标元素作为属性存储在事件对象中,这意味着事件将在其上触发的所有父元素都将知道事件的目标元素,因此事件首先被触发的位置。
这给我们带来了事件委托,因为如果事件在 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>
Run Code Online (Sandbox Code Playgroud)
在这些列表中添加项目:
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);
}
Run Code Online (Sandbox Code Playgroud)
删除项目:
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);
}
Run Code Online (Sandbox Code Playgroud)
事件委托正在处理使用容器元素上的事件处理程序冒泡的事件,但仅当事件发生在容器内与给定条件匹配的元素上时才激活事件处理程序的行为。这可以简化处理容器内元素的事件。
例如,假设您要处理对大表格中任何表格单元格的单击。您可以编写一个循环来将点击处理程序连接到每个单元格……或者您可以在表格上连接一个点击处理程序,并使用事件委托仅针对表格单元格(而不是表格标题,或表格中的空格)触发它在单元格周围排行等)。
当您要从容器中添加和删除元素时,它也很有用,因为您不必担心在这些元素上添加和删除事件处理程序;只需在容器上挂钩事件并在事件冒泡时处理该事件。
这是一个简单的例子(它是故意冗长的以允许内联解释):处理对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");
}
});
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)
在进入细节之前,让我们提醒自己 DOM 事件是如何工作的。
DOM 事件从文档分派到目标元素(捕获阶段),然后从目标元素冒泡回文档(冒泡阶段)。旧DOM3 事件规范中的这个图形(现已被取代,但图形仍然有效)显示得非常好:
并非所有事件都会冒泡,但大多数都会冒泡,包括click
.
上面代码示例中的注释描述了它是如何工作的。matches
检查元素是否与 CSS 选择器匹配,当然,如果您不想使用 CSS 选择器,您可以通过其他方式检查某些元素是否与您的条件匹配。
编写该代码是为了详细地调用各个步骤,但在模糊现代的浏览器上(如果您使用 polyfill,也可以在 IE 上),您可以使用closest
andcontains
代替循环:
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");
}
Run Code Online (Sandbox Code Playgroud)
现场示例:
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");
}
Run Code Online (Sandbox Code Playgroud)
// 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");
}
});
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
closest
检查您调用它的元素以查看它是否与给定的 CSS 选择器匹配,如果匹配,则返回相同的元素;如果不匹配,则检查父元素是否匹配,如果匹配则返回父元素;如果没有,它会检查父级的父级等。因此它会在祖先列表中找到与选择器匹配的“最近”元素。由于这可能会经过容器元素,因此上面的代码contains
用于检查是否找到了匹配的元素,它是否在容器内——因为通过将事件挂接到容器上,您已经表明您只想处理该容器内的元素.
回到我们的表格示例,这意味着如果您在表格单元格中有一个表格,它将与包含该表格的表格单元格不匹配:
<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>
Run Code Online (Sandbox Code Playgroud)
// 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");
}
});
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
小智 5
如果一个父级内部有许多元素,并且您希望处理它们上的事件 - 请不要将处理程序绑定到每个元素.相反,将单个处理程序绑定到其父级,并从event.target获取子级.该站点提供有关如何实现事件委派的有用信息. http://javascript.info/tutorial/event-delegation