Event Delegation
JavaScript Event Delegation (JavaScript事件委派)
Bubbling and capturing allows implementing an extremely powerful event handling pattern, known as event delegation. (冒泡和捕获允许实现极其强大的事件处理模式,称为事件委派。)
The concept of event delegation is fairly simple, although, it can seem complicated at first sight. (事件委托的概念相当简单,尽管乍看起来可能很复杂。)
The main idea is the following: while having multiple elements handled similarly, you don’t have to assign a handler for each of them. Instead, you can put a single handler on their common ancestor. It’s that simple.
To be more precise, event delegation allows avoiding to add event listeners to particular nodes. But, gives the opportunity of adding it to one parent. The event listener analyzes the bubbled events for finding appropriate child elements. (更准确地说,事件委派允许避免将事件侦听器添加到特定节点。但是,提供了将其添加到单个父项的机会。事件侦听器分析冒泡事件以查找适当的子元素。)
For a better understanding, let’s imagine a parent UL element, which includes child elements:
<!DOCTYPE html>
<html>
<head>
<title>Title of the Document</title>
</head>
<body>
<ul id="parent-list">
<li id="post_1">Post 1</li>
<li id="post_2">Post 2</li>
<li id="post_3">Post 3</li>
<li id="post_4">Post 4</li>
<li id="post_5">Post 5</li>
<li id="post_6">Post 6</li>
</ul>
</body>
</html>
When each of the child elements is clicked, something needs to happen. It is possible to add a separate event listener to every LI element. But if the elements are frequently added or removed, adding or removing event listeners would become a nightmare. The most elegant solution is adding an event listener to the parent UL element. You may wonder, how to know what element is clicked when adding the event listener to the parent. The answer is not complicated: at the moment the event bubbles up to the UL element, you need to check the event object’s target property for gaining a reference to the actually clicked node.
Here is an illustration of the basic delegation:
<!DOCTYPE html>
<html>
<head>
<title>Title of the Document</title>
</head>
<body>
<ul id="parent-list">
<li id="item_1">Item 1</li>
<li id="item_2">Item 2</li>
<li id="item_3">Item 3</li>
<li id="item_4">Item 4</li>
<li id="item_5">Item 5</li>
<li id="item_6">Item 6</li>
</ul>
<script>
// Get the element, add a click listener...
(//获取元素,添加点击侦听器...)
document.getElementById("parent-list").addEventListener("click", function(e) {
// e.target is the clicked element!
(//e.target是被点击的元素!)
// If it was a list item
(//如果是列表项)
if(e.target && e.target.nodeName == "LI") {
// List item found! Output the ID!
(//找到列表项!输出ID !)
alert("List item " + e.target.id.replace("item_", "") + " was clicked!");
}
});
</script>
</body>
</html>
Begin at adding a click event listener to the parent element. At the moment the event listener is triggered, you should check the element for ensuring it’s the type to react to. If it is not the appropriate one, the event may be ignored. (开始向父元素添加单击事件侦听器。在事件侦听器被触发的那一刻,您应该检查元素以确保它是要响应的类型。如果它不是合适的,则可以忽略该事件。)
Let’s consider a case, using parent DIV with a lot of children. In the example below, all you need to care about is an A tag along with the classA CSS class:
let div = document.createElement('div');
div.id = 'myDiv';
document.body.appendChild(div);
let a = document.createElement('a');
let link = document.createTextNode("Click on link");
a.appendChild(link);
a.id = 'a';
a.className = 'classA';
a.title = "Link";
a.href = "#";
div.appendChild(a);
// Get the parent DIV, add click listener...
document.getElementById("myDiv").addEventListener("click", function (e) {
// e.target was the clicked element
(//e.target是被点击的元素)
if (e.target && e.target.matches("a.classA")) {
alert("Welcome to w3cdoc!");
}
});
You can see, whether the element matches the desired one by applying Element.matches API, as well. (您也可以通过应用Element.matches API来查看元素是否与所需元素匹配。)
Let’s check out another example. (让我们看看另一个示例。)
Imagine you want to make a menu using the following buttons: “Save”, “Load”, “Search”. Also, there is an object with methods save, load, search. The most elegant solution to matching them is again adding a handler for the whole menu and data-action attributes for buttons, like this:
<button data-action="save">Click to Save</button>
The handler will read the attribute and execute the method, as follows:
<!DOCTYPE html>
<html>
<head>
<title>Title of the Document</title>
</head>
<body>
<div id="buttonList">
<button data-action="save">Save</button>
<button data-action="load">Load</button>
<button data-action="search">Search</button>
</div>
<script>
class List {
constructor(buttonElem) {
this._buttonElem = buttonElem;
buttonElem.onclick = this.onClick.bind(this); // (*)
}
save() {
alert('Saving...');
}
load() {
alert('Loading...');
}
search() {
alert('Searching...');
}
onClick(event) {
let action = event.target.dataset.action;
if(action) {
this[action]();
}
};
}
new List(buttonList);
</script>
</body>
</html>
It would be best if you noted that this.onClick is linked to this in (). It is a significant point, as otherwise this inside it would reference the DOM element (elem) and not the menu object. And, this[action] would not be the desired result. (最好注意this.onClick在()中链接到这一点。这是一个重要的点,否则它内部将引用DOM元素( elem )而不是菜单对象。而且,这个[动作]不会是理想的结果。)
Let’s highlight the main advantages of event delegation:
It’s not necessary to write the code for assigning a handler to every button. You just need to make a method, putting it in the markup. (-无需编写代码即可为每个按钮分配处理程序。您只需要创建一个方法,并将其放入标记中。)
The structure of HTML is flexible: it is possible to add or remove buttons at any time.
The “behavior” Pattern
The “behavior” Pattern (“行为”模式)
Event delegation can also be used to add “behaviors” to elements declaratively, using specific attributes and classes. (事件委派还可用于使用特定属性和类向元素声明性地添加“行为”。)
The pattern consists of two parts:
Adding a custom attribute to an element, which describes its behavior. A document-wide handler can track events: if an event occurs on an attributed element- it acts.
Behavior: Counter
Behavior: Counter
Let’s check out an example where the data-counter attribute adds a behavior:
<!DOCTYPE html>
<html>
<head>
<title>Title of the Document</title>
</head>
<body>
<!--Counter-->
<input type="button" value="1" data-counter>
<!--One more counter:-->
<input type="button" value="2" data-counter>
<script>
document.addEventListener('click', function(event) {
if(event.target.dataset.counter != undefined) { // if the attribute exists...
event.target.value++;
}
});
</script>
</body>
</html>
And the same thing in javascript:
// create first input tag
let input1 = document.createElement('input');
input1.id = 'input1';
input1.setAttribute('type', 'button');
input1.setAttribute('value', '1');
input1.setAttribute('data-counter', '');
document.body.appendChild(input1);
// create second input tag
let input2 = document.createElement('input');
input2.id = 'input1';
input2.setAttribute('type', 'button');
input2.setAttribute('value', '2');
input2.setAttribute('data-counter', '');
document.body.appendChild(input2);
document.addEventListener('click', function (event) {
if (event.target.dataset.counter != undefined) { // if the attribute exists...
event.target.value++;
}
});
In the case above, clicking a button increases its value but not the buttons. You can use as many attributes with data-counter as you like. You have the option of adding new ones to HTML at any time. Applying the event delegation, “extends” HTML. (在上述情况下,单击按钮会增加其值,但不会增加按钮。您可以将任意数量的属性与数据计数器一起使用。您可以随时向HTML中添加新内容。应用事件委派, “扩展” HTML。)
When you assign an event handler to the document object, you are recommended to use addEventListener, not document.on<event> always. That is because the latter may cause conflicts ( for example, new handlers can overwrite old ones).
Behavior: Toggler
Behavior: Toggler
In this part, we are going to look through another example of behavior. A click on an element with the data-toggle-id attribute may show or hide the element with the given id, as follows:
<!DOCTYPE html>
<html>
<head>
<title>Title of the Document</title>
</head>
<body>
<button data-toggle-id="mail-id">
Show the form
(显示表单)
</button>
<form id="mail-id" hidden>
Your mail:
<input type="email">
</form>
<script>
document.addEventListener('click', function(event) {
let id = event.target.dataset.toggleId;
if(!id) return;
let elem = document.getElementById(id);
elem.hidden = !elem.hidden;
});
</script>
</body>
</html>
So, now there is no need to know JavaScript to add toggling functionality to an element. Simply, you can use the data-toggle-id attribute. (因此,现在无需了解JavaScript即可向元素添加切换功能。简单地说,您可以使用data-toggle-id属性。)
Summary
Summary (概要)
Event delegation is super helpful: it’s one of the handiest patterns for DOM elements.
Most of the time, it is used for adding the same handling for multiple similar elements. (大多数情况下,它用于为多个相似元素添加相同的处理。)
The algorithm of event delegation is the following:
the first step is putting a single handler on the container. the next step is checking the event.target source element in the handler. then, if the event happened inside the desired element, you can handle the event. (第一步是在容器上放置单个处理程序。 下一步是检查处理程序中的event.target源元素。 然后,如果事件发生在所需的元素内部,则可以处理该事件。)
Event delegation has many benefits, such as:
It offers simple initialization and saves memory: there is no need to add multiple handlers.
It allows writing less code. (-它允许编写更少的代码。)
And, of course, DOM modifications. You can mass add or remove elements using innerHTML. (-当然还有DOM修改。您可以使用innerHTML批量添加或删除元素。)
Unfortunately, there are some limitations within event delegation:
First of all, the event has to be bubbling. But, some events can’t bubble. Besides, low-level handlers shouldn’t apply event.stopPropagation(). Secondly, the delegation might add CPU load, as the container-level handler reacts to events anywhere in the container. (首先,活动必须是冒泡的。 但是,有些活动不能冒泡。 此外,低级处理程序不应应用event.stopPropagation ()。 其次,委托可能会增加CPU负载,因为容器级处理程序会对容器中任何地方的事件做出反应。)