问题
Can anyone please explain event delegation in JavaScript and how is it useful?
回答1:
DOM event delegation is a mechanism of responding to ui-events via a single common parent rather than each child, through the magic of event "bubbling" (aka event propagation).
When an event is triggered on an element, the following occurs:
The event is dispatched to its target
EventTarget
and any event listeners found there are triggered. Bubbling events will then trigger any additional event listeners found by following theEventTarget
's parent chain upward, checking for any event listeners registered on each successive EventTarget. This upward propagation will continue up to and including theDocument
.
Event bubbling provides the foundation for event delegation in browsers. Now you can bind an event handler to a single parent element, and that handler will get executed whenever the event occurs on any of its child nodes (and any of their children in turn). This is event delegation. Here's an example of it in practice:
<ul onclick="alert(event.type + '!')">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
With that example if you were to click on any of the child <li>
nodes, you would see an alert of "click!"
, even though there is no click handler bound to the <li>
you clicked on. If we bound onclick="..."
to each <li>
you would get the same effect.
So what's the benefit?
Imagine you now have a need to dynamically add new <li>
items to the above list via DOM manipulation:
var newLi = document.createElement('li');
newLi.innerHTML = 'Four';
myUL.appendChild(newLi);
Without using event delegation you would have to "rebind" the "onclick"
event handler to the new <li>
element, in order for it to act the same way as its siblings. With event delegation you don't need to do anything. Just add the new <li>
to the list and you're done.
This is absolutely fantastic for web apps with event handlers bound to many elements, where new elements are dynamically created and/or removed in the DOM. With event delegation the number of event bindings can be drastically decreased by moving them to a common parent element, and code that dynamically creates new elements on the fly can be decoupled from the logic of binding their event handlers.
Another benefit to event delegation is that the total memory footprint used by event listeners goes down (since the number of event bindings go down). It may not make much of a difference to small pages that unload often (i.e. user's navigate to different pages often). But for long-lived applications it can be significant. There are some really difficult-to-track-down situations when elements removed from the DOM still claim memory (i.e. they leak), and often this leaked memory is tied to an event binding. With event delegation you're free to destroy child elements without risk of forgetting to "unbind" their event listeners (since the listener is on the ancestor). These types of memory leaks can then be contained (if not eliminated, which is freaking hard to do sometimes. IE I'm looking at you).
Here are some better concrete code examples of event delegation:
- How JavaScript Event Delegation Works
- Event Delegation versus Event Handling
- jQuery.delegate is event delegation + selector specification
- jQuery.on uses event delegation when passed a selector as the 2nd parameter
- Event delegation without a JavaScript library
- Closures vs Event delegation: takes a look at the pros of not converting code to use event delegation
- Interesting approach PPK uncovered for delegating the focus and blur events (which do not bubble)
回答2:
Event delegation allows you to avoid adding event listeners to specific nodes; instead, the event listener is added to one parent. That event listener analyzes bubbled events to find a match on child elements.
JavaScript Example :
Let's say that we have a parent UL element with several child elements:
<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>
Let's also say that something needs to happen when each child element is clicked. You could add a separate event listener to each individual LI element, but what if LI elements are frequently added and removed from the list? Adding and removing event listeners would be a nightmare, especially if addition and removal code is in different places within your app. The better solution is to add an event listener to the parent UL element. But if you add the event listener to the parent, how will you know which element was clicked?
Simple: when the event bubbles up to the UL element, you check the event object's target property to gain a reference to the actual clicked node. Here's a very basic JavaScript snippet which illustrates event delegation:
// 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!");
}
});
Start by adding a click event listener to the parent element. When the event listener is triggered, check the event element to ensure it's the type of element to react to. If it is an LI element, boom: we have what we need! If it's not an element that we want, the event can be ignored. This example is pretty simple -- UL and LI is a straight-forward comparison. Let's try something more difficult. Let's have a parent DIV with many children but all we care about is an A tag with the classA CSS class:
// 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
回答3:
dom event delegation is something different from the computer science definition.
It refers to handling bubbling events from many elements, like table cells, from a parent object, like the table. It can keep the code simpler, especially when adding or removing elements, and saves some memory.
回答4:
Delegation is a technique where an object expresses certain behavior to the outside but in reality delegates responsibility for implementing that behaviour to an associated object. This sounds at first very similar to the proxy pattern, but it serves a much different purpose. Delegation is an abstraction mechanism which centralizes object (method) behavior.
Generally spoken: use delegation as alternative to inheritance. Inheritance is a good strategy, when a close relationship exist in between parent and child object, however, inheritance couples objects very closely. Often, delegation is the more flexible way to express a relationship between classes.
This pattern is also known as "proxy chains". Several other design patterns use delegation - the State, Strategy and Visitor Patterns depend on it.
回答5:
The delegation concept
If there are many elements inside one parent, and you want to handle events on them of them - don’t bind handlers to each element. Instead, bind the single handler to their parent, and get the child from event.target. This site provides useful info about how to implement event delegation. http://javascript.info/tutorial/event-delegation
回答6:
Event delegation is handling an event that bubbles using an event handler on a container element, but only activating the event handler's behavior if the event happened on an element within the container that matches a given condition. This can simplify handling events on elements within the container.
For instance, suppose you want to handle a click on any table cell in a big table. You could write a loop to hook up a click handler to each cell...or you could hook up a click handler on the table and use event delegation to trigger it only for table cells (and not table headers, or the whitespace within a row around cells, etc.).
It's also useful when you're going to be adding and removing elements from the container, because you don't have to worry about adding and removing event handlers on those elements; just hook the event on the container and handle the event when it bubbles.
Here's a simple example (it's intentionally verbose to allow for inline explanation): Handling a click on any td
element in a container table:
// 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>
Before going into the details of that, let's remind ourselves how DOM events work.
DOM events are dispatched from the document to the target element (the capturing phase), and then bubble from the target element back to the document (the bubbling phase). This graphic in the old DOM3 events spec (now superceded, but the graphic's still valid) shows it really well:
Not all events bubble, but most do, including click
.
The comments in the code example above describe how it works. matches checks to see if an element matches a CSS selector, but of course you can check for whether something matches your criteria in other ways if you don't want to use a CSS selector.
That code is written to call out the individual steps verbosely, but on vaguely-modern browsers (and also on IE if you use a polyfill), you can use closest and contains instead of the loop:
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");
}
Live Example:
// 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.
Going back to our table example, that means that if you have a table within a table cell, it won't match the table cell containing the 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;
}
<!-- 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>
回答7:
It's basically how association is made to the element. .click applies to the current DOM, while .on (using delegation) will continue to be valid for new elements added to the DOM after event association.
Which is better to use, I'd say it depends on the case.
Example:
<ul id="todo">
<li>Do 1</li>
<li>Do 2</li>
<li>Do 3</li>
<li>Do 4</li>
</ul>
.Click Event:
$("li").click(function () {
$(this).remove ();
});
Event .on:
$("#todo").on("click", "li", function () {
$(this).remove();
});
Note that I've separated the selector in the .on. I'll explain why.
Let us suppose that after this association, let us do the following:
$("#todo").append("<li>Do 5</li>");
That is where you will notice the difference.
If the event was associated via .click, task 5 will not obey the click event, and so it will not be removed.
If it was associated via .on, with the selector separate, it will obey.
回答8:
A delegate in C# is similar to a function pointer in C or C++. Using a delegate allows the programmer to encapsulate a reference to a method inside a delegate object. The delegate object can then be passed to code which can call the referenced method, without having to know at compile time which method will be invoked.
See this link --> http://www.akadia.com/services/dotnet_delegates_and_events.html
回答9:
Event delegation makes use of two often overlooked features of JavaScript events: event bubbling and the target element.When an event is triggered on an element, for example a mouse click on a button, the same event is also triggered on all of that element’s ancestors. This process is known as event bubbling; the event bubbles up from the originating element to the top of the DOM tree.
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.
回答10:
Event Delegation
Attach an event listener to a parent element that fires when an event occurs on a child element.
Event PropagationWhen an event moves through the DOM from child to a parent element, that's called Event Propagation, because the event propagates, or moves through the DOM.
In this example, an event (onclick) from a button gets passed to the parent paragraph.
$(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>
Codepen
来源:https://stackoverflow.com/questions/1687296/what-is-dom-event-delegation