Wie funktioniert Capturing oder Bubbling in Javascript?


21.12.2020, 19:25

Bin Anfänger

1 Antwort

Das Event selbst, ist das z.B. ein Klick?

Ja.

Zu diesem Event wird ein Event-Objekt angelegt, welches Informationen über das Event beinhaltet (z.B. Koordinaten eines Klicks, o.ä.).

Der Klick geht dann durch den "ganzen Aufbau" und löst dann, wenn ein Eventlistener vorhanden ist etwas aus?

Nach Auslösen des Events wird der Propagation Path (auch event chain genannt) gebaut, durch den iteriert wird (capturing phase). Der besteht natürlich nur aus Elementen, die überhaupt gerendert werden. Wenn es auf einem der Elemente dieses Pfad gebundene Handler für das Event gibt,werden sie aufgerufen und das Event-Objekt übergeben. Andernfalls geht es nur bis zum Zielelement (target phase), welches das Event nun mit seinem gebundenen Event Handler bearbeiten kann.

Danach kann das Event Bubbling (bubbling phase) durchgeführt werden (das gilt zumindest für die Maus-Events, bestimmte Events wie focus/blur haben keine Bubbling Phase). Es geht also wieder den Baum nach oben. Wieder wird auf dem Weg je Element geschaut, ob es passende Handler gibt, die ausgeführt werden können.

Ob Handler in der Capturing Phase oder Bubbling Phase behandelt werden sollen, wird bei der Registration des Listeners mit dem dritten Argument entschieden:

element.addEventListener("click", function(evt) { /* ... */}, true); // capture

Der Standardwert ist false (= Registrierung für bubbling).

Bei Ausführung der Phasen kann ein Handler auch dafür sorgen, dass bestimmte Handler ignoriert oder gar die komplette Eventkette abgebrochen wird (s. preventDefault- und stopPropagation-Methoden).

Zu guter Letzt noch ein Beispiel zum Nachvollziehen.

HTML:

<section id="top">top
  <article id="middle">middle
    <div id="bottom">bottom</div>
  </article>
</section>

CSS:

section,
article,
div {
  margin-top: 10px;
  padding: 10px;
}

#top {
  background: yellow;
}

#middle {
  background: orange;
}

#bottom {
  background: red;
}

JavaScript:

const topLayer = document.getElementById("top");
const middleLayer = document.getElementById("middle");
const bottomLayer = document.getElementById("bottom");

const capture = false;

topLayer.addEventListener("click", function(evt) {
  console.log("top");
  printInfo(evt);
}, capture);

middleLayer.addEventListener("click", function(evt) {
  console.log("middle");
  printInfo(evt);
}, capture);

bottomLayer.addEventListener("click", function(evt) {
  console.log("bottom");
  printInfo(evt);
}, capture);

function printInfo(evt) {
  console.log(evt.currentTarget.tagName);
  console.log(evt.target.tagName);
}

Demo

Dieses Beispiel beinhaltet drei ineinander verschachtelte Boxen mit Klick-Listenern. Wenn du sie jeweils anklickst, wird die Capturing / Bubbling-Phase in der Konsole demonstriert (je nach Wert von capture).

Du könntest dir mit

console.log(evt.eventPhase);

sogar noch anzeigen lassen, in welcher Phase ein Handler ausgeführt wird.

Tibor15 
Fragesteller
 21.12.2020, 20:48

Ok, vielen Dank. Also triggert das Event auf seinem Weg "Eventlistener" und löst sie dadurch aus. Mit true/false wird dann jeweils einer der Pfade, was zum Auslösen führen würde unterdrückt? Und was ist mit "Andernfalls geht es nur bis zum Zielelement (target phase), welches das Event nun mit seinem gebundenen Event Handler bearbeiten kann." gemeint? Inwiefern gebundenen Element? Sorry,bin Anfänger auf diesem Gebiet.

0
regex9  21.12.2020, 21:55
@Tibor15

Ich denke, es wäre nicht schlecht, einmal das klassische Observer-Prinzip zu zeigen. Dieses bezieht sich zunächst einmal nur auf die Bindung zwischen Element und Event Handler.

Dazu ein bildlicheres, abstrakteres Beispiel:

class Superstar {
  fans = [];

  addFan(fan) {
    fans.push(fan);
  }

  sendAutographToFans() {
    const standardPhrases = "...";

    for (const fan of fans) {
      fan.receiveAutograph(new Card(fan.name, standardPhrases));
    }
  }
}

const cryingGirl = new Fan("Katie");
const paul = new Superstar();
paul.addFan(cryingGirl);

Die Superstar-Klasse ist hier das Oberservable (beobachtbares Subjekt). Im Webbrowser wäre das ein HTML-Element. Der Superstar kennt alle seine Fans (die Observer / Listener). Dazu speichert er sie in einer Liste. Sie sind also an das Observable gebunden.

Um einen Observer zu binden, wird die addFan-Methode verwendet. Im Vergleich dazu wird im Browser die addEventListener-Methode genutzt, die natürlich noch etwas komplexer ist, da sie ja noch zwischen Eventtypen, etc. unterscheidet.

Nun kommt es zu irgendeinem Event. Vielleicht weil sich ein Zustand des Superstar-Objekts ändert (er wird todkrank und entschließt sich nun schnell, noch all seine Briefpost zu beantworten). In dem Zuge wird auch sendAutographToFans aufgerufen. Die Funktion läuft über alle gebundenen Observer und ruft deren Event Handler auf (receiveAutograph). Jedem Handler wird - an der Stelle kein Event-Objekt, aber ersatzweise eine Karte übergeben, die spezielle Informationen enthält. In dem Fall ein paar Standardfloskeln und eine persönliche Widmung für den Fan (Einsatz dessen Namens).

Soweit, wie gesagt - nur die Bindung zwischen einem Element und seinen Observern. Nun müsste das Beispiel etwas ausgeweitet werden, um auch die Event-Kette mit in Betracht zu ziehen.

Im Webbrowser wird für die Behandlung der meisten Events die Liste an Elementen in der Eventkette zweimal(!) durchlaufen (Capturing + Bubbling). Für das Superstar-Beispiel könnte man es so umsetzen, dass man zwei Listen anlegt, mit einer Unterscheidung zwischen den größten Fans (die sofort behandelt werden) und den weniger größten Fans (die ihre Post erst später bekommen):

class Superstar {
  greatestFans = [];
  fans = [];

  addFan(fan, isABigFan) {
    if (isABigFan) {
      greatestFans.push(fan);
    }
    else {
      fans.push(fan);
    }
  }

  sendAutographToFans(forBigFans) {
    const standardPhrases = "...";

    if (forBigFans) {
      for (const fan of greatestFans) {
        fan.receiveAutograph(new Card(fan.name, standardPhrases));
      }
    }
    else {
      for (const fan of fans) {
        fan.receiveAutograph(new Card(fan.name, standardPhrases));
      }
    }
  }
}

Um nun noch eine Pseudo-Event-Kette zusammenzuwürfeln - angenommen, das ist ein Array voller Superstar-Objekte:

const beatles = [ john, paul, george, ringo ];

Und hier noch zwei Fans (John und George gehen mal leer aus^^):

const cryingGirl = new Fan("Katie");
paul.addFan(cryingGirl, false);

const hyperventilatingGirl = new Fan("Nicole");
ringo.addFan(hyperventilatingGirl, true);

Nach Browserprinzip würde jedes Element einmal durchlaufen werden und jedes würde seine sendAutographToFans-Methode aufrufen. In diesem ersten Lauf würde nur Ringos Fan eine Postkarte erhalten.

Danach erfolgt ein zweiter Lauf über alle Elemente (rückwärts). Diesmal würde Pauls Fan Post bekommen.

Andernfalls geht es nur bis zum Zielelement ( target phase), welches das Event nun mit seinem gebundenen Event Handler bearbeiten kann.

Vielleicht war mein Ausdruck etwas missverständlich. Die Event-Kette hat halt einen Anfang und ein Ende. Der Schritt, wo das Ende erreicht wird, wird fachlich als eigene Phase bezeichnet, das ist alles. Praktisch wird auf jedem Element geschaut, ob es dort gebundene Handler gibt, die ausgeführt werden können. Wenn dem nicht so ist, passiert halt nichts.

0
Tibor15 
Fragesteller
 22.12.2020, 07:41
@regex9

Wow, vielen Dank für diese super Erklärung.

0
Tibor15 
Fragesteller
 22.12.2020, 07:56
@regex9

Die Erklärung hab ich soweit verstanden, jedoch frage ich mich wo die Variable forBigFans herkommt und weshalb, wenn es rückwärts abläuft, Paul eine Nachricht bekommt?

0
regex9  22.12.2020, 13:30
@Tibor15

Während der Event-Behandlung weiß das Event stets, in welcher Phase es sich gerade befindet (erneuter Hinweis auf das eventPhase-Property). In meinem Beispiel sollte forBigFans dieses Property / diese Information symbolisch darstellen.

const beatles = [ john, paul, george, ringo ];

// capturing
for (const beatle of beatles) {
  beatle.sendAutographToFans(true);
}

// bubbling
for (const beatle of beatles.reverse()) {
  beatle.sendAutographToFans(false);
}
(...) und weshalb, wenn es rückwärts abläuft, Paul eine Nachricht bekommt?

Weil Katie nur als normaler Fan registriert wurde, wird ihr erst in der Bubbling Phase eine Nachricht geschickt.

paul.addFan(cryingGirl, false);
0
Tibor15 
Fragesteller
 22.12.2020, 15:31
@regex9

Ok, also ist laut deinem Modell zuerst Ringo dran, da er einen Fan hat, auf den forBigFans als true zutrifft. Das ist mir klar. Aber weshalb wird Kattie erst bei Paul einen Brief bekommen? Die Funktion sendet doch erst einen Brief, wenn man ein großer Fan ist. Sie ist aber doch keiner. Ansonsten ist es mir klar. Danke

0
Tibor15 
Fragesteller
 22.12.2020, 15:34
@Tibor15

fan.receiveAutograph(new Card(fan.name, standardPhrases));

}

Müsste es stattdessen nicht biggestFan heißen, da die doch zuerst eine Nachricht bekommen sollen?

0
Tibor15 
Fragesteller
 23.12.2020, 10:24
@regex9

Ok, sorry, wenn ich grad zu blöd bin, aber von der Logik wird die Funktion, die einen Brief versendet doch nur aufgerufen, wenn man ein großer Fan ist. Das ist bei Katie doch nie der Fall, weshalb bekommt sie dann trotzdem Post?

0
regex9  23.12.2020, 16:58
@Tibor15

Nein. Schau dir doch einmal den Programmablauf an: https://www.gutefrage.net/frage/wie-funktioniert-capturing-oder-bubbling-in-javascript#comment-275559414

sendAutographToFans wird einmal immer mit true und einmal mit false aufgerufen. Folge von da aus der Definition in der Funktion. Ich habe extra darauf geachtet, den Code einfach und sprechend zu formulieren. Wenn es aber für dich notwendig ist, füge Konsoleneingaben ein oder nutze die Debugging Tools deines Webbrowsers (Chrome, Firefox), um den Ablauf zu prüfen.

0