Czy wszystkie zdarzenia jquery powinny być powiązane z $ (dokument)?

164

Skąd to pochodzi

Kiedy po raz pierwszy nauczyłem się jQuery, zwykle dołączałem takie zdarzenia:

$('.my-widget a').click(function() {
    $(this).toggleClass('active');
});

Gdy dowiedziałem się więcej o szybkości selektora i delegowaniu zdarzeń, przeczytałem w kilku miejscach, że „delegowanie zdarzeń jQuery przyspieszy Twój kod”. Zacząłem więc pisać taki kod:

$('.my-widget').on('click','a',function() {
    $(this).toggleClass('active');
});

Był to również zalecany sposób replikacji zachowania przestarzałego zdarzenia .live (). Co jest dla mnie ważne, ponieważ wiele moich witryn przez cały czas dynamicznie dodaje / usuwa widżety. Powyższe nie zachowuje się jednak dokładnie tak, jak .live (), ponieważ tylko elementy dodane do już istniejącego kontenera „.my-widget” otrzymają takie zachowanie. Jeśli dynamicznie dodam kolejny blok html po uruchomieniu tego kodu, elementy te nie otrzymają powiązanych z nimi zdarzeń. Lubię to:

setTimeout(function() {
    $('body').append('<div class="my-widget"><a>Click does nothing</a></div>');
}, 1000);


Co chcę osiągnąć:

  1. stare zachowanie .live () // oznaczające przypisywanie zdarzeń do jeszcze nieistniejących elementów
  2. zalety .on ()
  3. najszybsza wydajność do wiązania zdarzeń
  4. Prosty sposób na zarządzanie wydarzeniami

Dołączam teraz wszystkie zdarzenia takie jak to:

$(document).on('click.my-widget-namespace', '.my-widget a', function() {
    $(this).toggleClass('active');
});

Co wydaje się spełniać wszystkie moje cele. (Tak, z jakiegoś powodu jest wolniejsze w IE, nie mam pojęcia dlaczego?) Jest szybkie, ponieważ tylko jedno zdarzenie jest powiązane z pojedynczym elementem, a selektor pomocniczy jest oceniany tylko wtedy, gdy zdarzenie się wydarzy (proszę poprawić mnie, jeśli jest źle). Przestrzeń nazw jest niesamowita, ponieważ ułatwia przełączanie nasłuchiwania zdarzeń.

Moje rozwiązanie / pytanie

Zaczynam więc myśleć, że zdarzenia jQuery powinny być zawsze powiązane z $ (dokument).
Czy jest jakiś powód, dla którego nie chcesz tego robić?
Czy można to uznać za najlepszą praktykę? Jeśli nie, dlaczego?

Jeśli przeczytałeś to wszystko, dziękuję. Doceniam wszelkie / wszystkie opinie / spostrzeżenia.

Założenia:

  1. Korzystanie z jQuery obsługującego .on()// co najmniej wersję 1.7
  2. Chcesz, aby wydarzenie zostało dodane do dynamicznie dodawanej zawartości

Odczyty / Przykłady:

  1. http://24ways.org/2011/your-jquery-now-with-less-suck
  2. http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
  3. http://www.jasonbuckboyer.com/playground/speed/speed.html
  4. http://api.jquery.com/on/
Bryknięcie
źródło

Odpowiedzi:

215

Nie - NIE należy wiązać wszystkich delegowanych programów obsługi zdarzeń z documentobiektem. To prawdopodobnie najgorszy scenariusz, jaki można stworzyć.

Po pierwsze, delegowanie zdarzeń nie zawsze przyspiesza kod. W niektórych przypadkach jest to korzystne, aw niektórych nie. Z delegowania zdarzeń należy korzystać, gdy faktycznie jest potrzebne delegowanie zdarzeń i gdy jest to korzystne. W przeciwnym razie należy powiązać programy obsługi zdarzeń bezpośrednio z obiektami, w których ma miejsce zdarzenie, ponieważ zazwyczaj będzie to bardziej wydajne.

Po drugie, NIE powinieneś wiązać wszystkich delegowanych wydarzeń na poziomie dokumentu. Właśnie z tego .live()powodu został uznany za przestarzały, ponieważ jest to bardzo nieefektywne, gdy w ten sposób jest powiązanych wiele wydarzeń. W przypadku obsługi zdarzeń delegowanych DUŻO bardziej efektywne jest powiązanie ich z najbliższym rodzicem, który nie jest dynamiczny.

Po trzecie, nie wszystkie wydarzenia działają lub wszystkie problemy można rozwiązać za pomocą delegacji. Na przykład, jeśli chcesz przechwycić kluczowe zdarzenia w kontrolce wejściowej i zablokować wprowadzanie nieprawidłowych kluczy do kontrolki wejściowej, nie możesz tego zrobić za pomocą delegowanej obsługi zdarzeń, ponieważ do czasu, gdy zdarzenie przechodzi do delegowanej procedury obsługi, już ma zostały przetworzone przez kontrolkę wejściową i jest już za późno, aby wpłynąć na to zachowanie.

Oto sytuacje, w których delegowanie zdarzeń jest wymagane lub korzystne:

  • Gdy obiekty, na których przechwytujesz zdarzenia, są dynamicznie tworzone / usuwane i nadal chcesz przechwytywać na nich zdarzenia bez konieczności jawnego ponownego wiązania programów obsługi zdarzeń za każdym razem, gdy tworzysz nowy.
  • Gdy masz wiele obiektów, z których wszystkie wymagają dokładnie tego samego modułu obsługi zdarzeń (gdzie partie to co najmniej setki). W takim przypadku bardziej wydajne może być w czasie konfiguracji powiązanie jednego delegowanego programu obsługi zdarzeń zamiast setek lub więcej bezpośrednich programów obsługi zdarzeń. Uwaga: delegowana obsługa zdarzeń jest zawsze mniej wydajna w czasie wykonywania niż bezpośrednia obsługa zdarzeń.
  • Kiedy próbujesz uchwycić (na wyższym poziomie w dokumencie) zdarzenia, które występują w dowolnym elemencie w dokumencie.
  • Gdy Twój projekt wyraźnie używa propagacji zdarzeń i metody stopPropagation () w celu rozwiązania problemu lub funkcji na stronie.

Aby lepiej to zrozumieć, należy zrozumieć, jak działają procedury obsługi zdarzeń delegowanych w jQuery. Kiedy zadzwonisz do czegoś takiego:

$("#myParent").on('click', 'button.actionButton', myFn);

Instaluje na #myParentobiekcie ogólną procedurę obsługi zdarzeń jQuery . Gdy zdarzenie kliknięcia przechodzi do tej delegowanej procedury obsługi zdarzeń, jQuery musi przejść przez listę delegowanych programów obsługi zdarzeń dołączonych do tego obiektu i sprawdzić, czy element źródłowy zdarzenia pasuje do któregokolwiek z selektorów w delegowanych programach obsługi zdarzeń.

Ponieważ selektory mogą być dość zaangażowane, oznacza to, że jQuery musi przeanalizować każdy selektor, a następnie porównać go z charakterystyką oryginalnego celu zdarzenia, aby sprawdzić, czy pasuje do każdego selektora. To nie jest tania operacja. Nie ma nic wielkiego, jeśli jest tylko jeden z nich, ale jeśli umieścisz wszystkie selektory w obiekcie dokumentu, a były setki selektorów do porównania z każdym pojedynczym zdarzeniem, może to poważnie pogorszyć wydajność obsługi zdarzeń.

Z tego powodu chcesz skonfigurować delegowane programy obsługi zdarzeń, aby delegowana procedura obsługi zdarzeń była jak najbliżej obiektu docelowego. Oznacza to, że mniej zdarzeń będzie przechodzić przez każdą delegowaną procedurę obsługi zdarzeń, poprawiając w ten sposób wydajność. Umieszczanie wszystkich delegowanych zdarzeń w obiekcie dokumentu to najgorsza możliwa wydajność, ponieważ wszystkie zdarzenia propagowane muszą przejść przez wszystkie delegowane programy obsługi zdarzeń i zostać ocenione względem wszystkich możliwych delegowanych selektorów zdarzeń. Właśnie dlatego .live()jest przestarzały, ponieważ tak właśnie .live()było i okazał się bardzo nieefektywny.


Aby więc osiągnąć optymalną wydajność:

  1. Używaj obsługi zdarzeń delegowanych tylko wtedy, gdy faktycznie zapewnia potrzebną funkcję lub zwiększa wydajność. Nie używaj go zawsze, ponieważ jest to łatwe, ponieważ w rzeczywistości go nie potrzebujesz. W rzeczywistości działa gorzej w czasie wysyłania zdarzenia niż bezpośrednie wiązanie zdarzeń.
  2. Dołącz delegowane programy obsługi zdarzeń do najbliższego elementu nadrzędnego do źródła zdarzenia, jak to możliwe. Jeśli używasz delegowanej obsługi zdarzeń, ponieważ masz elementy dynamiczne, dla których chcesz przechwytywać zdarzenia, wybierz najbliższego rodzica, który sam nie jest dynamiczny.
  3. Użyj łatwych do oceny selektorów dla delegowanych programów obsługi zdarzeń. Jeśli śledzisz, jak działa obsługa delegowanych zdarzeń, zrozumiesz, że delegowaną procedurę obsługi zdarzeń trzeba wielokrotnie porównywać z wieloma obiektami, więc wybierz możliwie jak najbardziej wydajny selektor lub dodaj proste klasy do obiektów, aby można było użyć prostszych selektorów. zwiększyć wydajność obsługi zdarzeń delegowanych.
jfriend00
źródło
2
Niesamowite. Dziękuję za szybki i szczegółowy opis. Ma to sens, że czas wysyłania zdarzenia wydłuży się dzięki użyciu .on (), ale myślę, że wciąż mam problem z wyborem między zwiększonym czasem wysyłania zdarzenia a zespołem przetwarzającym początkową stronę. Generalnie jestem za krótszym początkowym czasem strony. Na przykład, jeśli mam 200 elementów na stronie (i więcej, gdy zdarzają się zdarzenia), jest to około 100 razy droższe przy początkowym ładowaniu (ponieważ musi dodać 100 detektorów zdarzeń), zamiast dodawać pojedyncze nasłuchiwanie zdarzeń do link do
Buck
Chciałem też powiedzieć, że mnie przekonałeś - Nie wiąż wszystkich wydarzeń z $ (dokument). Zwiąż jak najwięcej z najbliższym niezmiennym rodzicem. Myślę, że nadal będę musiał decydować indywidualnie dla każdego przypadku, kiedy delegacja zdarzenia jest lepsza niż bezpośrednie wiązanie zdarzenia z elementem. A kiedy potrzebuję zdarzeń powiązanych z zawartością dynamiczną (która nie ma niezmiennego rodzica), myślę, że będę robił to, co @Vega zaleca poniżej i po prostu „powiążę procedurę obsługi po wstawieniu zawartości do ( ) DOM ”, używając parametru context jQuery w moim selektorze.
Buck
5
@ user1394692 - jeśli masz dużo prawie identycznych elementów, sensowne może być użycie do nich delegowanych procedur obsługi zdarzeń. Mówię to w mojej odpowiedzi. Gdybym miał gigantyczną tabelę z 500 wierszami i miał ten sam przycisk w każdym wierszu określonej kolumny, użyłbym jednego delegowanego programu obsługi zdarzeń na stole do obsługi wszystkich przycisków. Ale gdybym miał 200 elementów i wszystkie potrzebowały własnej unikalnej obsługi zdarzeń, zainstalowanie 200 delegowanych programów obsługi zdarzeń nie jest szybsze niż zainstalowanie 200 bezpośrednio powiązanych programów obsługi zdarzeń, ale delegowana obsługa zdarzeń może być znacznie wolniejsza po wykonaniu zdarzenia.
jfriend00
Jesteś idealna. Kompletnie się zgadzam! Czy rozumiem, że to najgorsza rzecz, jaką mogę zrobić - $(document).on('click', '[myCustomAttr]', function () { ... });?
Artem Fitiskin
Używanie pjax / turbolinks stanowi interesujący problem, ponieważ wszystkie elementy są dynamicznie tworzone / usuwane (poza ładowaniem pierwszej strony). Czy więc lepiej jest jednorazowo powiązać wszystkie zdarzenia z wybranymi obiektami document, czy też powiązać je z bardziej szczegółowymi zaznaczeniami page:loadi rozłączyć te zdarzenia page:after-remove? Hmmm
Dom Christie
9

Delegowanie zdarzeń to technika pisania programów obsługi, zanim element faktycznie zaistnieje w DOM. Ta metoda ma swoje wady i powinna być używana tylko wtedy, gdy masz takie wymagania.

Kiedy należy używać delegowania zdarzeń?

  1. Gdy powiążesz wspólną procedurę obsługi dla większej liczby elementów, które wymagają tej samej funkcjonalności. (Np. Najechanie na wiersz tabeli)
    • W tym przykładzie, gdybyś musiał powiązać wszystkie wiersze za pomocą bezpośredniego wiązania, utworzyłbyś w końcu procedurę obsługi n dla n wierszy w tej tabeli. Używając metody delegowania, możesz w końcu obsłużyć te wszystkie w jednym prostym module obsługi.
  2. Gdy częściej dodajesz zawartość dynamiczną w DOM (np. Dodawanie / usuwanie wierszy z tabeli)

Dlaczego nie powinieneś używać delegowania zdarzeń?

  1. Delegowanie zdarzenia jest wolniejsze w porównaniu z wiązaniem zdarzenia bezpośrednio z elementem.
    • Porównuje selektor celu na każdym trafionym bąbelku, porównanie będzie równie kosztowne, jak skomplikowane.
  2. Brak kontroli nad propagowaniem zdarzenia, dopóki nie trafi w element, z którym jest powiązane.

PS: Nawet w przypadku zawartości dynamicznej nie musisz używać metody delegowania zdarzeń, jeśli łączysz procedurę obsługi po wstawieniu zawartości do DOM. (Jeśli zawartość dynamiczna nie jest dodawana często usuwana / ponownie dodawana)

Selvakumar Arumugam
źródło
0

Chciałbym dodać kilka uwag i kontrargumentów do odpowiedzi jfriend00. (głównie moje opinie oparte na moich przeczuciach)

Nie - NIE należy wiązać wszystkich delegowanych programów obsługi zdarzeń z obiektem dokumentu. To prawdopodobnie najgorszy scenariusz, jaki można stworzyć.

Po pierwsze, delegowanie zdarzeń nie zawsze przyspiesza kod. W niektórych przypadkach jest to korzystne, aw niektórych nie. Z delegowania zdarzeń należy korzystać, gdy faktycznie jest potrzebne delegowanie zdarzeń i gdy jest to korzystne. W przeciwnym razie należy powiązać programy obsługi zdarzeń bezpośrednio z obiektami, w których ma miejsce zdarzenie, ponieważ zazwyczaj będzie to bardziej wydajne.

Chociaż prawdą jest, że wydajność może być nieco lepsza, jeśli zamierzasz zarejestrować i zdarzenie tylko dla jednego elementu, uważam, że nie jest to sprzeczne z korzyściami skalowalności, jakie przynosi delegowanie. Uważam również, że przeglądarki coraz wydajniej sobie z tym radzą, chociaż nie mam na to dowodów. Moim zdaniem delegacja na imprezy to droga!

Po drugie, NIE powinieneś wiązać wszystkich delegowanych wydarzeń na poziomie dokumentu. Właśnie dlatego .live () został uznany za przestarzały, ponieważ jest bardzo nieefektywny, gdy w ten sposób jest powiązanych wiele zdarzeń. W przypadku obsługi zdarzeń delegowanych DUŻO bardziej efektywne jest powiązanie ich z najbliższym rodzicem, który nie jest dynamiczny.

W pewnym sensie się z tym zgadzam. Jeśli masz 100% pewności, że zdarzenie będzie miało miejsce tylko w kontenerze, sensowne jest powiązanie zdarzenia z tym kontenerem, ale nadal będę spierał się przeciwko powiązaniu zdarzeń bezpośrednio z elementem wyzwalającym.

Po trzecie, nie wszystkie zdarzenia działają lub wszystkie problemy można rozwiązać za pomocą delegacji. Na przykład, jeśli chcesz przechwycić kluczowe zdarzenia w kontrolce wejściowej i zablokować wprowadzanie nieprawidłowych kluczy do kontrolki wejściowej, nie możesz tego zrobić z delegowaną obsługą zdarzeń, ponieważ do czasu, gdy zdarzenie przechodzi do delegowanej procedury obsługi, już ma zostały przetworzone przez kontrolkę wejściową i jest już za późno, aby wpłynąć na to zachowanie.

To po prostu nieprawda. Zobacz ten kodPen: https://codepen.io/pwkip/pen/jObGmjq

document.addEventListener('keypress', (e) => {
    e.preventDefault();
});

Pokazuje, jak można zapobiec wpisywaniu przez użytkownika, rejestrując zdarzenie naciśnięcia klawisza w dokumencie.

Oto sytuacje, w których delegowanie zdarzeń jest wymagane lub korzystne:

Gdy obiekty, na których przechwytujesz zdarzenia, są dynamicznie tworzone / usuwane i nadal chcesz przechwytywać na nich zdarzenia bez konieczności jawnego ponownego wiązania programów obsługi zdarzeń za każdym razem, gdy tworzysz nowy. Gdy masz wiele obiektów, z których wszystkie wymagają dokładnie tego samego modułu obsługi zdarzeń (gdzie partie to co najmniej setki). W takim przypadku bardziej wydajne może być w czasie konfiguracji powiązanie jednego delegowanego programu obsługi zdarzeń zamiast setek lub więcej bezpośrednich programów obsługi zdarzeń. Uwaga: delegowana obsługa zdarzeń jest zawsze mniej wydajna w czasie wykonywania niż bezpośrednia obsługa zdarzeń.

Chciałbym odpowiedzieć tym cytatem z https://ehsangazar.com/optimizing-javascript-event-listeners-for-performance-e28406ad406c

Event delegation promotes binding as few DOM event handlers as possible, since each event handler requires memory. For example, let’s say that we have an HTML unordered list we want to bind event handlers to. Instead of binding a click event handler for each list item (which may be hundreds for all we know), we bind one click handler to the parent unordered list itself.

Ponadto performance cost of event delegation googlewyszukiwanie w Google zwraca więcej wyników na korzyść delegowania wydarzeń.

Kiedy próbujesz uchwycić (na wyższym poziomie w dokumencie) zdarzenia, które występują w dowolnym elemencie w dokumencie. Gdy Twój projekt wyraźnie używa propagacji zdarzeń i metody stopPropagation () w celu rozwiązania problemu lub funkcji na stronie. Aby lepiej to zrozumieć, należy zrozumieć, jak działają procedury obsługi zdarzeń delegowanych w jQuery. Kiedy zadzwonisz do czegoś takiego:

$ ("# myParent"). on ('click', 'button.actionButton', myFn); Instaluje ogólny program obsługi zdarzeń jQuery na obiekcie #myParent. Gdy zdarzenie kliknięcia przechodzi do tej delegowanej procedury obsługi zdarzeń, jQuery musi przejść przez listę delegowanych programów obsługi zdarzeń dołączonych do tego obiektu i sprawdzić, czy element źródłowy zdarzenia pasuje do któregokolwiek z selektorów w delegowanych programach obsługi zdarzeń.

Ponieważ selektory mogą być dość zaangażowane, oznacza to, że jQuery musi przeanalizować każdy selektor, a następnie porównać go z charakterystyką oryginalnego celu zdarzenia, aby sprawdzić, czy pasuje do każdego selektora. To nie jest tania operacja. To nic wielkiego, jeśli jest tylko jeden z nich, ale jeśli umieścisz wszystkie selektory na obiekcie dokumentu, a były setki selektorów do porównania z każdym pojedynczym zdarzeniem, może to poważnie pogorszyć wydajność obsługi zdarzeń.

Z tego powodu chcesz skonfigurować delegowane programy obsługi zdarzeń, aby delegowana procedura obsługi zdarzeń była jak najbliżej obiektu docelowego. Oznacza to, że mniej zdarzeń będzie przechodzić przez każdą delegowaną procedurę obsługi zdarzeń, poprawiając w ten sposób wydajność. Umieszczanie wszystkich delegowanych zdarzeń w obiekcie dokumentu to najgorsza możliwa wydajność, ponieważ wszystkie zdarzenia propagowane muszą przejść przez wszystkie delegowane programy obsługi zdarzeń i zostać ocenione względem wszystkich możliwych delegowanych selektorów zdarzeń. Właśnie dlatego .live () jest przestarzała, ponieważ tak właśnie zrobiła .live () i okazała się bardzo nieefektywna.

Gdzie to jest udokumentowane? Jeśli to prawda, to jQuery wydaje się obsługiwać delegowanie w bardzo nieefektywny sposób, a wtedy moje kontrargumenty powinny być stosowane tylko do waniliowego JS.

Mimo to: chciałbym znaleźć oficjalne źródło wspierające to twierdzenie.

:: EDYTOWAĆ ::

Wygląda na to, że jQuery rzeczywiście robi propagację zdarzeń w bardzo nieefektywny sposób (ponieważ obsługuje IE8) https://api.jquery.com/on/#event-performance

Tak więc większość moich argumentów odnosi się tylko do klasycznego JS i nowoczesnych przeglądarek.

Jules Colle
źródło