Zdarzenia#
Delegacja#
Gdy na stronie internetowej znajduje się wiele elementów, a każdy z nich ma dołączony co najmniej jeden uchwyt zdarzenia (np. dla zdarzenia typu click
), może mieć to wpływ na wydajność aplikacji. Dołączenie każdej funkcji zwrotnej ma swoją cenę w postaci cięższych stron (więcej kodu JavaScript lub w samych znacznikach) oraz dłuższego czasu wykonania. Im więcej obiektów trzeba ruszyć i zmodyfikować (w szczególności węzłów DOM), tym wolniejsza jest aplikacja, gdyż faza dołączania obsługi zdarzeń odbywa się zwykle przy zdarzeniach typu DOMContentLoaded
lub load
, czyli w czasie znacznego obciążenia strony dużą liczą interakcji.
Dołączanie obsługi zdarzeń zajmuje więc czas przetwarzania, a ponadto przeglądarka musi śledzić każdy element obsługi, co powoduje nadmierne zużycie pamięci. Wreszcie, duża liczba elementów obsługujących zdarzenia może nigdy nie być potrzebna, choćby ze względu na to, że użytkownik zazwyczaj jest w stanie kliknąć tylko jeden przycisk lub odnośnik, a nie 50 jednocześnie, to też spora część pracy potrzebnej do rejestrowania wszystkich tych uchwytów byłaby zbędna.
Prostą i elegancką techniką obsługi wielu zdarzeń DOM jest delegowanie zdarzeń (event delegation). Jest ona oparta na fakcie, że zdarzenie (obiekty) przemieszczają się po ścieżce propagacji, zgodnie z mechanizmem przepływu zdarzeń. Delegację można zastosować zarówno dla fazy przechwytywania jak i bąbelkowania, ale w praktyce najczęściej wykorzystywane jest jedynie bąbelkowanie. Wystarczy dołączyć tylko jeden uchwyt zdarzenia do elementu opakowującego (przodka), aby obsłużyć wszystkie zdarzenia, które odnoszą się do elementów pochodnych (potomków) tego opakowania nadrzędnego. Po wyzwoleniu zdarzenia w elemencie potomnym zostanie ono przeniesione do elementu nadrzędnego (faza bąbelkowania), gdzie zostanie obsłużone. Ta pojedyncza funkcja obsługi może rozróżniać, który element potomny jest celem zdarzenia, i w razie konieczności pobierać dodatkowe informacje za pośrednictwem jakiegoś atrybutu tego elementu.
Załóżmy, że mamy kod z klasyczną nieuporządkowaną listą HTML, gdzie w elementach listy umieściliśmy odsyłacze:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<ul>
<li><a href="https://www.google.pl">Google</a></li>
<li><a href="https://www.mozilla.org">Mozilla</a></li>
<li><a href="http://www.opera.com">Opera</a></li>
<li><a href="http://www.apple.com">Apple</a></li>
<li><a href="http://www.microsoft.com">Microsoft</a></li>
</ul>
</body>
</html>
Gdy użytkownik kliknie pierwsze łącze Google
(jest ono celem zdarzenia), zdarzenie najpierw rozchodzi się od okna przeglądarki (obiektu window
) do celu zdarzenia, chociaż nas ta faza nie obchodzi, a następnie odbija się od celu zdarzenia i bąbelkuje z powrotem do okna przeglądarki. W fazie bąbelkowania zdarzenie zostaje odebrane przez wszystkich przodków celu zdarzenia, czyli przez element li
, potem przez ul
, przez body
i tak dalej. Dzięki temu można dołączyć do elementu nadrzędnego ul
tylko jeden uchwyt zdarzenia i otrzymywać powiadomienia dla wszystkich zdarzeń, które dotyczą elementów pochodnych. Robimy to w następujący sposób:
<!DOCTYPE html>
<html>
<head>
<script>
// Uruchom po całkowitym załadowaniu dokumentu
window.onload = function(){
var list = document.getElementsByTagName("ul")[0];
list.addEventListener("click", function(e){
if (e.target.tagName.toLowerCase() == "a"){
e.preventDefault();
e.stopPropagation();
alert("Cele zdarzenia e.target.tagName: " + e.target.tagName
+ "\nTreść celu zdarzenia e.target.textContent: " + e.target.textContent
+ "\nAdres celu zdarzenia e.target.href: " + e.target.href
);
}
else{
e.stopPropagation();
}
}, false);
}
</script>
</head>
<body>
<ul>
<li><a href="https://www.google.pl">Google</a></li>
<li><a href="https://www.mozilla.org">Mozilla</a></li>
<li><a href="http://www.opera.com">Opera</a></li>
<li><a href="http://www.apple.com">Apple</a></li>
<li><a href="http://www.microsoft.com">Microsoft</a></li>
</ul>
</body>
</html>
Przy delegowaniu zdarzeń warto wstrzymywać dalszą propagację zdarzenia metodą Event.stopPropagation()
, dzięki czemu pozostałe uchwyty zdarzeń rejestrowane na dalszych przodkach nie zostaną wywołane. Czasami przydatne jest także anulowanie domyślnego zachowania metodą Event.preventDefault()
, tak jak miało to miejsce dla odsyłaczy w powyższym przykładzie. Jeśli element nadrzędny, do którego dołączamy uchwyt zdarzenia będzie zawierał wiele pozagnieżdżanych elementów, które spełniają nasz filtr oparty na nazwie znacznika, a tylko część z nich powinna inaczej reagować na kliknięcie, to zawsze można zrobić filtrację względem jakiegoś atrybut, np. klasy, adresu lub innego podobnego rozwiązania uszczegóławiającego takie elementy.
Kolejną korzyścią wynikającą z delegowania zdarzeń jest to, że każdy potomek spełniający warunki filtracji, nawet ten dodawany dynamicznie do elementu nadrzędnego, również będzie reagował na procedurę obsługi zdarzenia. Dlatego w powyższym przykładzie każdy nowy odsyłacz dodany do listy już po załadowaniu strony wywoła procedurę obsługi zdarzenia typu click
.
Wadą delegacji zdarzeń jest konieczność filtrowania tych elementów, którymi nie jesteśmy zainteresowani, co owocuje kilkoma dodatkowymi wierszami kodu. Z drugiej strony zalety znacząco przewyższają wady, w związku z czym jest to bardzo elastyczny i przydatny mechanizm, z którym każdy programista prędzej czy później powinien się zapoznać.