Delegacja i propagacja zdarzeń w JavaScript
        1. Propagacja zdarzeń
Propagacja zdarzeń to sposób, w jaki zdarzenia poruszają się w drzewie DOM. Wyróżniamy dwa kierunki propagacji:
- Faza capturing (przechwytywanie, w dół) – zdarzenie przechodzi od elementu 
documentw dół do elementu, który je wywołał. - Faza bubbling (bąbelkowanie, w górę) – zdarzenie przechodzi od elementu, który je wywołał, do góry w drzewie DOM aż do 
document. 
Przykład propagacji zdarzeń
Załóżmy, że mamy następujący HTML:
<div id="parent">
    <button id="child">Click me</button>
</div>
I dodajemy dwa nasłuchiwacze:
document.getElementById("parent").addEventListener("click", () => {
    console.log("PARENT clicked");
});
document.getElementById("child").addEventListener("click", () => {
    console.log("CHILD clicked");
});
Jeśli klikniesz #child, zobaczysz w konsoli:
CHILD clicked!
PARENT clicked!
Dzieje się tak, bo zdarzenie bąbelkuje (przechodzi od dziecka do rodzica).
Zatrzymanie propagacji zdarzeń
Jeśli nie chcemy, aby zdarzenie bąbelkowało w górę, możemy użyć event.stopPropagation():
document.getElementById("child").addEventListener("click", event => {
    event.stopPropagation(); // Zatrzymuje propagację
    console.log("CHILD clicked");
});
Teraz po kliknięciu #child konsola wyświetli tylko: CHILD clicked , #parent nie otrzyma zdarzenia.
Wymuszenie przechwytywania zdarzeń
Domyślnie nasłuchiwanie zdarzeń odbywa się w fazie bąbelkowania, co zonacza, że najpierw wywołane zostanie zdarzenie dla #child , a następnie dla #parent . Można  jednak odwrócić tą kolejność, przypisując zdarzenie do do fazy przechwytywania, poprzez przekazanie true jako trzeciego argumentu addEventListener:
document.getElementById("parent").addEventListener("click", () => {
    console.log("PARENT clicked (capturing)");
}, true);
Teraz #parent zostanie uruchomiony przed #child, ponieważ obsługuje zdarzenia w fazie przechwytywania.
2. Delegacja zdarzeń
Delegacja zdarzeń to technika polegająca na przypisywaniu event listenera do elementu nadrzędnego zamiast do każdego potomka osobno. Przydatne, gdy elementy dynamicznie pojawiają się w DOM (np. za pomocą ajax).
document.getElementById("parent").addEventListener("click", event => {
  if (event.target.classList.contains("child")) {
    console.log("Child clicked: ", event.target);
  }
});
Jeśli później dodamy nowy element:
const newChild = document.createElement("div");
newChild.classList.add("child");
newChild.textContent = "Nowy element";
document.getElementById("parent").appendChild(newChild);
Nadal będziemy mogli wykryć kliknięcia w nowy element, mimo że nie dodaliśmy mu osobnego addEventListener.