javascript - Bør alle jQuery-hendelser knyttes til $(document)?
Hvor dette kommer fra
Da jeg først lærte jQuery, festet jeg vanligvis hendelser slik:
$('.my-widget a').click(function() {
$(this).toggleClass('active');
});
Etter å ha lært mer om selektorhastighet og hendelsesdelegering, leste jeg flere steder at "jQuery-hendelsesdelegering vil gjøre koden din raskere." Så jeg begynte å skrive kode slik:
$('.my-widget').on('click','a',function() {
$(this).toggleClass('active');
});
Dette var også den anbefalte måten å replikere oppførselen til den utdaterte .live()-hendelsen på. Dette er viktig for meg siden mange av sidene mine dynamisk legger til/fjerner widgets hele tiden. Ovenstående oppfører seg imidlertid ikke nøyaktig som .live(), siden kun elementer som legges til i den allerede eksisterende beholderen '.my-widget' vil få oppførselen. Hvis jeg dynamisk legger til et annet HTML-blokk etter at den koden har kjørt, vil ikke disse elementene få tilknyttet hendelsene. Slik:
setTimeout(function() {
$('body').append('<div class="my-widget"><a>Click does nothing</a></div>');
}, 1000);
Hva jeg ønsker å oppnå:
- den gamle oppførselen til .live()/betyr å koble hendelser til elementer som ennå ikke eksisterer
- fordelene med .on()
- raskeste ytelse for å binde hendelser
- Enkel måte å administrere arrangementer på
Jeg legger nå ved alle hendelser på denne måten:
$(document).on('click.my-widget-namespace', '.my-widget a', function() {
$(this).toggleClass('active');
});
Som ser ut til å oppfylle alle målene mine. (Ja, det er tregere i IE av en eller annen grunn, har ingen idé hvorfor?) Det er raskt fordi kun én enkelt hendelse er knyttet til et enkelt element, og den sekundære velgeren blir kun evaluert når hendelsen inntreffer (rett meg gjerne hvis dette er feil her). Navnerommet er fantastisk siden det gjør det lettere å bytte avlytter for hendelsen.
Min løsning/spørsmål
Så jeg begynner å tenke at jQuery-hendelser alltid bør bindes til $(document).
Er det noen grunn til at man ikke skulle ønske å gjøre dette?
Kan dette anses som en beste praksis? Hvis ikke, hvorfor?
Hvis du har lest hele dette, takk. Jeg setter pris på all slags tilbakemelding/alle innspill.
Antakelser:
- Ved å bruke jQuery som støtter
.on()/minst versjon 1.7 - Du vil at hendelsen skal legges til i dynamisk lagt til innhold
Lesinger/Eksempler:
- http://24ways.org/2011/your-jquery-now-with-less-suck
- http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
- http://www.jasonbuckboyer.com/playground/speed/speed.html
- http://api.jquery.com/on/
Svar #1
Nei - du bør IKKE binde alle delegerte hendelsesbehandlere til document-objektet. Det er sannsynligvis det dårligst presterende scenariet du kan lage.
Først og fremst gjør ikke hendelsesdelegering alltid koden din raskere. I noen tilfeller er det fordelaktig, og i noen tilfeller ikke. Du bør bruke hendelsesdelegering når du faktisk trenger hendelsesdelegering og når du drar nytte av det. Ellers bør du binde hendelseshåndterere direkte til objektene der hendelsen skjer, da dette vanligvis vil være mer effektivt.
For det andre bør du IKKE binde alle delegerte hendelser på dokumentnivå. Dette er nettopp grunnen til at .live() ble avskrevet, fordi dette er veldig ineffektivt når du har mange hendelser bundet på denne måten. For delegerte hendelseshåndtering er det MYE mer effektivt å binde dem til nærmeste overordnede som ikke er dynamisk.
For det tredje, ikke alle hendelser fungerer eller alle problemer kan løses med delegering. For eksempel, hvis du vil fange tasthendelser på en inndata-kontroll og blokkere ugyldige taster fra å bli skrevet inn i inndata-kontrollen, kan du ikke gjøre det med delegerte hendelseshåndtering fordi når hendelsen bobler opp til den delegerte håndtereren, har den allerede blitt behandlet av inndata-kontrollen, og det er for sent å påvirke denne oppførselen.
Her er tider når hendelsesdelegering er nødvendig eller fordelaktig:
- Når objektene du fanger hendelser på blir opprettet/fjernet dynamisk, og du fortsatt ønsker å fange hendelser på dem uten å måtte eksplisitt binde hendelsesbehandlere på nytt hver gang du oppretter en ny.
- Når du har mange objekter som alle vil ha nøyaktig samme hendelsesbehandler (hvor mange er minst hundrevis). I dette tilfellet kan det være mer effektivt ved oppsett å binde én delegert hendelsesbehandler i stedet for hundrevis eller flere direkte hendelsesbehandlere. Merk at delegert hendelseshåndtering alltid er mindre effektiv ved kjøretid enn direkte hendelsesbehandlere.
- Når du prøver å fange (på et høyere nivå i dokumentet ditt) hendelser som skjer på et hvilket som helst element i dokumentet.
- Når designet ditt eksplisitt bruker hendelsesbobling og stopPropagation() for å løse et problem eller en funksjon på siden din.
For å forstå dette litt mer, må man forstå hvordan jQuery delegerte hendelsesbehandlere fungerer. Når du kaller noe slik:
$("#myParent").on('click', 'button.actionButton', myFn);
Den installerer en generell jQuery-hendelseshåndterer på #myParent-objektet. Når et klikk-hendelse bobler opp til denne delegerte hendelseshåndtereren, må jQuery gå gjennom listen over delegerte hendelseshåndterere som er knyttet til dette objektet og se om det opprinnelige elementet for hendelsen stemmer overens med noen av selektorene i de delegerte hendelseshåndtererne.
Fordi selektorer kan være ganske komplekse, betyr dette at jQuery må analysere hver selektor og deretter sammenligne den med egenskapene til det opprinnelige hendelsesmålet for å se om den matcher hver selektor. Dette er ikke en billig operasjon. Det er ikke noe problem hvis det bare er én av dem, men hvis du legger alle selektorene på dokumentobjektet og det var hundrevis av selektorer å sammenligne med hver eneste boblede hendelse, kan dette seriøst begynne å hemme ytelsen til hendelseshåndtering.
Av denne grunn vil du sette opp dine delegerte hendelseshåndterere slik at en delegerte hendelseshåndterer er så nær målobjektet som praktisk mulig. Dette betyr at færre hendelser vil boble gjennom hver delegerte hendelseshåndterer, noe som forbedrer ytelsen. Å plassere alle delegerte hendelser på dokumentobjektet er den verste mulige ytelsen fordi alle boblede hendelser må gå gjennom alle delegerte hendelseshåndterere og vurderes mot alle mulige delegerte hendelsesselektorer. Dette er akkurat grunnen til at .live() er avskrevet fordi dette var hva .live() gjorde, og det viste seg å være veldig ineffektivt.
Så, for å oppnå optimalisert ytelse:
- Bruk kun delegert hendelseshåndtering når det faktisk gir en funksjon du trenger eller øker ytelsen. Ikke bare bruk det alltid fordi det er enkelt, for når du egentlig ikke trenger det, fungerer det faktisk dårligere ved hendelsesdistribusjon enn direkte hendelsesbinding.
- Fest delegerte hendelsesbehandlere til nærmeste forelder til kilden til hendelsen som mulig. Hvis du bruker delegering av hendelser fordi du har dynamiske elementer som du vil fange hendelser for, velg da den nærmeste forelderen som ikke selv er dynamisk.
- Bruk lett-å-vurdere selektorer for delegerte hendelsesbehandlere. Hvis du har fulgt med på hvordan delegering av hendelser fungerer, vil du forstå at en delegerte hendelsesbehandler må sammenlignes med mange objekter mange ganger, så det å velge så effektiv selektor som mulig eller legge til enkle klasser til objektene dine slik at enklere selektorer kan brukes, vil øke ytelsen til delegerte hendelsesbehandlere.
Svar #2
Hendelsesdelegering er en teknikk for å skrive dine håndterere før elementet faktisk eksisterer i DOM. Denne metoden har sine egne ulemper og bør kun brukes hvis du har slike krav.
Når bør du bruke hendelsesdelegering?
Når du binder en vanlig håndterer for flere elementer som trenger samme funksjonalitet. (F.eks.: tabellrad-hviling)
- I eksemplet, hvis du måtte binde alle rader ved å bruke direkte binding, ville du ende opp med å lage n håndterere for n rader i den tabellen. Ved å bruke delegeringsmetoden kunne du ende opp med å håndtere alle de med 1 enkel håndterer.
- Når du legger til dynamisk innhold oftere i DOM (f.eks.: Legg til/fjern rader fra en tabell)
Hvorfor du ikke bør bruke hendelsesdelegering?
Hendelsesdelegering er tregere sammenlignet med å binde hendelsen direkte til elementet.
- Den sammenligner målvælgeren på hver boble den treffer, sammenligningen vil være like kostbar som den er komplisert.
- Ingen kontroll over hendelsesboblingen før den treffer elementet den er knyttet til.
PS: Selv for dynamisk innhold trenger du ikke å bruke hendelsesdelegasjonsmetoden hvis du binder håndtereren etter at innholdet er satt inn i DOM-en. (Hvis det dynamiske innholdet legges til og ikke ofte fjernes/gjenlegges)
