-
Notifications
You must be signed in to change notification settings - Fork 17
/
event-handling-effizient.html
144 lines (132 loc) · 11.4 KB
/
event-handling-effizient.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>JavaScript: Effiziente Ereignis-Verarbeitung: Event-Delegation und Capturing</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="js-doku.css">
</head>
<body>
<div id="nav">
<p>Hier entsteht eine <strong>JavaScript-Dokumentation</strong> von <a href="https://molily.de/">molily</a>. Derzeit ist sie noch lückenhaft, wächst aber nach und nach. Kommentare und Feedback werden gerne per <a href="mailto:[email protected]">E-Mail</a> entgegen genommen.</p>
<p class="contents-link"><a href="./">Zum Inhaltsverzeichnis</a></p>
</div>
<h1>JavaScript: Effiziente Ereignis-Verarbeitung</h1>
<div class="section" id="delegation">
<h2>Event-Delegation</h2>
<p>Wenn zahlreiche Elemente im Dokument überwacht werden sollen, ist es sehr aufwändig und langsam, diese herauszusuchen und bei jedem denselben Event-Handler zu registrieren. Bei einer solchen Aufgabenstellung können Sie vom <a href="event-handling-objekt.html#bubbling">Bubbling-Effekt</a> profitieren, das ist das Aufsteigen der Ereignisse im DOM-Baum.</p>
<p>Man macht sich die Verschachtelung der Elemente im DOM-Baum zunutze und überwacht die Ereignisse von verschiedenen Elementen bei einem gemeinsamen, höherliegenden Element, zu dem die Ereignisse aufsteigen. Diese Technik nennt sich <dfn>Event-Delegation</dfn> (englisch <em lang="en">delegation</em> für Übertragung von Aufgaben). Dabei wird einem zentralen Element die Aufgabe übertragen, die Ereignisse zu verarbeiten, die bei seinen Nachfahrenelementen passieren.</p>
<p>Event-Delegation eignet sich insbesondere dann, wenn viele gleichförmige Elemente in Menüs, Link-Listen, Formularen oder Tabellen JavaScript-Interaktivität benötigen. Ohne Event-Delegation müsste man jedes Element einzeln ansprechen, um dort immer denselben Event-Handler zu registrieren.</p>
<p>Nehmen wir beispielsweise eine Liste mit Links zu Bildern. Wenn JavaScript aktiv ist, soll das Vollbild dokumentintern eingeblendet werden. Ein ähnliches Beispiel hatten wir bereits beim <a href="event-handling-objekt.html#standardaktion">Unterdrücken der Standardaktion</a> – die Umsetzung der Einblendung bleibt weiterhin ausgeklammert.</p>
<p>Wir gehen von folgendem HTML-Gerüst aus:</p>
<pre>
<ul id="bilderliste">
<li>
<a href="bilder/bild1.jpg">
<img src="bilder/thumbnail1.jpg" alt="">
Ebru und Robin auf dem Empire State Building
</a>
</li>
<li>
<a href="bilder/bild2.jpg">
<img src="bilder/thumbnail2.jpg" alt="">
Noël und Francis vor dem Taj Mahal
</a>
</li>
<li>
<a href="bilder/bild3.jpg">
<img src="bilder/thumbnail3.jpg" alt="">
Isaak und Ahmet vor den Pyramiden von Gizeh
</a>
</li>
<!-- … viele weitere Links mit Thumbnails … -->
</ul>
</pre>
<p>Beim Klick auf einen der Links soll nun das verlinkte Bild eingeblendet werden. Anstatt jedem <code>a</code>-Element einzeln einen Handler zuzuweisen, registrieren wir ihn beim gemeinsamen Vorfahrenelement <code>ul</code> mit der ID <code>bilderliste</code>:</p>
<pre>
document.getElementById('bilderliste')
.addEventListener('click', bilderlistenKlick, false);
</pre>
<p>In der angegebenen Handler-Funktion <code>bilderlistenKlick</code> findet nun die Überprüfung des Zielelementes statt.</p>
<pre>
function bilderlistenKlick (event) {
var elementName = event.target.nodeName;
var aElement = null;
// Überprüfe, ob das Zielelement ein Link oder ein Bild im Link ist:
if (elementName === 'A') {
// Falls ein Link geklickt wurde, speichere das Zielelement
// in der Variable aElement:
aElement = event.target;
} else if (elementName === 'IMG') {
// Falls das Thumbnail-Bild geklickt wurde, suche
// das zugehörige Link-Element. Es ist das Elternelement:
aElement = event.target.parentNode;
}
// Zeige das Vollbild, wenn das Zielelement
// ein Link ist oder in einem Link liegt:
if (aElement) {
zeigeVollbild(aElement);
// Unterdrücke die Standardaktion:
event.preventDefault();
}
// Andernfalls mache nichts.
}
</pre>
<p>In dieser Funktion wird das Zielelement des Ereignisses angesprochen und dessen Elementname überprüft. Wenn ein <code>a</code>-Element geklickt wurde, muss es sich um einen Link auf ein Bild handeln und das Vollbild soll eingeblendet werden.</p>
<p>Das alleine wäre bereits mit der Abfrage <code>if (event.target.nodeName === 'A')</code> zu erledigen. Das Beispiel hat allerdings bewusst eine Schwierigkeit eingebaut, um Ihnen das Event-Bubbling und das Arbeiten mit dem Zielelement näher zu bringen: In den <code>a</code>-Elementen liegen zusätzlich <code>img</code>-Elemente für die Thumbnails. Wenn der Anwender auf diese klickt, soll das Vollbild ebenfalls eingeblendet werden. In dem Fall ist jedoch nicht das <code>a</code>-Element das Zielelement, sondern das <code>img</code>-Element.</p>
<p>Aus diesem Grund muss die Abfrage erweitert werden: Handelt es sich um ein <code>a</code>-Element <em>oder</em> um ein Element, das darin liegt? Falls das Zielelement ein <code>img</code>-Element ist, steigen wir mittels <code>parentNode</code> zu seinem <code>a</code>-Elternelement auf.</p>
<p>Falls ein <code>a</code>-Element ermittelt werden könnte, wird schließlich die Funktion <code>zeigeVollbild</code> mit dem Elementobjekt als Parameter aufgerufen. Das Gerüst dieser Funktion sieht so aus:</p>
<pre>
function zeigeVollbild(aElement) {
// Empfange das Elementobjekt als ersten Parameter und
// lese dessen href-Attribut mit der Bild-Adresse aus:
var bildAdresse = aElement.href;
// Blende das Bild ein, auf das der Link zeigt.
// (Die genaue Umsetzung ist an dieser Stelle ausgeklammert.)
window.alert(bildAdresse);
}
</pre>
<p>Dieses Beispiel soll Ihnen die grundlegende Funktionsweise von Event-Delegation veranschaulichen:</p>
<ol>
<li>Es gibt eine Handler-Funktion, die alle Ereignisse eines Types überwacht, die von seinen Nachfahrenelementen aufsteigen.</li>
<li>Darin wird das Ereignis untersucht und insbesondere das Zielelement überprüft.</li>
<li>Wenn das Zielelement gewissen Kriterien entspricht (z.B. einem bestimmten Elementyp oder einer Klasse angehört), wird auf das Ereignis reagiert.</li>
</ol>
<p>Wie Sie schon bei diesem einfachen Beispiel sehen, ist eine aufwändige Untersuchung des DOM-Elementbaumes rund um das Zielelement nötig. Bei Event-Delegation stellt sich oft die Frage, ob das Zielelement in einem anderen Element enthalten ist, auf das gewisse Kriterien zutreffen. Eine allgemeinere und vielseitig einsetzbare Lösung werden Sie später noch kennenlernen.</p>
</div>
<div class="section" id="capturing">
<h2>Capturing</h2>
<p>Capturing (englisch für <em>Einfangen</em>) ist eine Phase beim <a href="event-handling-objekt.html#bubbling">Event-Fluss</a>. Wir haben sie bereits kurz angesprochen, ohne sie näher zu erklären. Der DOM-Standard definiert <strong>drei Phasen</strong>, in denen ein Ereignis durch den DOM-Elementbaum wandert (Event-Fluss genannt) und Handler auslöst:</p>
<ol>
<li><strong>Capturing-Phase</strong> (Absteigen zum Zielelement): Das Ereignis steigt vom obersten Dokument-Knoten im Elementbaum hinab bis zum Zielelement. Auf diesem Weg werden alle Handler ausgeführt, die für den Ereignistyp für die Capturing-Phase registriert wurden.
<li><strong>Target-Phase</strong> (Zielelement-Phase): Das Ereignis erreicht sein Zielelement und löst die entsprechenden Handler aus, die dort für die Bubbling-Phase registriert wurden.</li>
<li><strong>Bubbling-Phase</strong> (Aufsteigen vom Zielelement): Das Ereignis steigt ausgehend vom Zielelement wieder in der Element-Hierarchie auf. Es durchläuft alle Vorfahrenelemente und löst dort die relevanten Handler aus.</li>
</ol>
<p>Alle bisher beschriebenen Modelle, ausgehend vom <a href="event-handling-grundlagen.html#traditionelles-event-handling">traditionellen</a> über <a href="event-handling-fortgeschritten.html#dom-events">W3C DOM Events</a> und dem <a href="event-handling-fortgeschritten.html#microsoft">Microsoft-Modell</a>, haben Handler für die Bubbling-Phase registriert. Wie wir uns das Bubbling zunutze machen, haben wir bereits bei der <a href="#delegation">Event-Delegation</a> kennengelernt.</p>
<p>Capturing ist ein weiterer Ansatz, um Ereignisse effizient zu überwachen: Wir können ein Event-Handler bei einem höherliegenden Element registrieren, um die Ereignisse zu überwachen, die bei vielen Nachfahrenelemente passieren.</p>
<p>Der Unterschied zwischen Bubbling und Capturing folgender: Nicht alle Ereignisse haben eine Bubbling-Phase. Das heißt, nicht alle Ereignisse steigen auf und lösen die entsprechenden Handler bei ihren Vorfahrenelementen aus. Das hat durchaus seinen Sinn, macht aber die beschriebene Event-Delegation unmöglich. Gäbe es das Event-Capturing nicht, wären Sie gezwungen, alle nicht aufsteigenden Ereignisse direkt bei ihren Zielelementen zu überwachen. Mithilfe des Event-Capturings können Sie auch solche Ereignisse zentral überwachen – denn <em>jedes</em> Ereignis hat eine Capturing-Phase.</p>
<p>Event-Capturing ist nur unter Verwendung der standardisierten Methode <code>addEventListener</code> möglich. Um Event-Handler für die Capturing-Phase zu registrieren, nutzen Sie wie gewohnt <code>addEventListener</code>, setzen jedoch den dritten Boolean-Parameter auf <code>true</code>:</p>
<pre>document.addEventListener("focus", captureHandler, <strong>true</strong>);</pre>
<p>Die Vorteile des Capturings liegen darin, nicht aufsteigende Ereignisse bei einem höherliegenden Element zu verarbeiten.
<p>Folgende Ereignisse beispielsweise steigen nicht auf:</p>
<ul>
<li><code>load</code>, z.B. bei Bildern, Objekten und Iframes</li>
<li><code>focus</code> und <code>blur</code>. (Als alternative aufsteigende Ereignisse gibt es allerdings <code>focusin</code> und <code>focusout</code>.)</li>
<li><code>mouseenter</code> und <code>mouseleave</code>. (Die Ereignisse <code>mouseover</code> und <code>mouseout</code> hingegen steigen auf.)</li>
</ul>
<p>Da die Capturing-Phase die erst im Event-Fluss ist, ist es möglich, ein Ereignis schon in dieser Phase abzufangen. Ruft man in der Capturing-Phase die <a href="event-handling-objekt.html#bubbling-verhindern"><code>stopPropagation</code>-Methode des Event-Objekts</a> auf, so wird der Fluss abgebrochen und die Ziel- und Bubbling-Phase fallen aus. Das Ereignis erreicht somit das Zielelement nicht.</p>
</div>
<div class="sequence-navigation">
<p class="next"><a href="browser.html" rel="next">Browserübergreifende Entwicklung</a></p>
<p class="prev"><a href="event-handling-onload.html" rel="prev">Scripte beim Laden des Dokuments ausführen</a></p>
</div>
<div id="footer">
<p><strong>JavaScript-Dokumentation</strong> · <a href="./">Zum Inhaltsverzeichnis</a></p>
<p>Autor: <a href="https://molily.de/">molily</a> · Kontakt: <a href="mailto:[email protected]">[email protected]</a></p>
<p>Lizenz: <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/de/">Creative Commons Namensnennung - Weitergabe unter gleichen Bedingungen 3.0</a></p>
<p><a href="https://github.com/molily/javascript-einfuehrung">JavaScript-Einführung auf Github</a></p>
<p>Kostenloses Online-Buch und E-Book:<br><a href="https://testing-angular.com" lang="en" hreflang="en">Testing Angular – A Guide to Robust Angular Applications</a> (englisch)</p>
</div>
<script src="js-doku.js"></script>
</body>
</html>