JavaScript Händelser Från Ground Up

Nästan allt du gör i JavaScript ska börja när något händer: användaren klickar på en knapp, hänger över en meny, trycker på en nyckel, du får tanken. Det är ganska enkelt att göra med bibliotek som jQuery, men vet du hur man kopplar upp händelser i rå JavaScript? Idag ska jag bara lära dig det!


Välkommen till JavaScript-evenemang

Hantering av JavaScript-händelser är inte raketvetenskap. Självklart finns det skillnader i webbläsaren (vad förväntade du dig?), Men när du hänger på det och bygger egna hjälparfunktioner för att jämnt spela, kommer du vara glad att du vet det. Även om du fortfarande planerar att använda ett bibliotek för den här typen av saker är det bra att veta vad som händer under ytan. Nu kör vi.


Nivå 0 Händelsehantering

Den mest grundläggande händelsehanteringen i JavaScript är händelsehantering på nivå 0. Det är ganska enkelt: Tilldela bara din funktion till händelseegenskapen på elementet. Observera:

 var element = document.getElementById ('myElement'); element.onclick = function () alert ('din funktion här'); ;

När jag har elementet ställer jag in onclick egenskap lika med en funktion. Nu när element är klickad, den funktionen kommer att köras, och vi får se en varningsruta.
Men hur visste jag att jag skulle använda onclick? Du kan få en lista över tillgängliga händelser på Wikipedia. Skönheten om Nivå 0-metoden är att den stöds på alla webbläsare som används i stor utsträckning idag. Nackdelen är att du bara kan registrera varje händelse en gång med varje element. Så detta kommer bara att orsaka en varning:

 element.onclick = funktion () alert ('funktion 1'); ; element.onclick = function () alert ('Jag åsidosätter funktion 1');

Endast den andra funktionen kommer att köras när elementet klickas, eftersom vi omplacerade onclick-egenskapen. För att ta bort en händelsehanterare kan vi bara ställa in den till null.

 element.onclick = null;

Även om det bara ger grundläggande funktionalitet, gör det konsekventa stödet för alla webbläsare händelsehantering i nivå 0 ett giltigt alternativ för superklara projekt.


Full Screencast



Event Object och This

Nu när du värmer upp i JavaScript-händelser, låt oss diskutera två viktiga aspekter innan du går vidare till bättre tekniker: händelseobjektet och detta nyckelord. När en händelse körs finns det mycket information om det evenemang som du kanske vill veta: vilken nyckel pressades? vilket element klickades? Klickade användaren med vänster, höger eller mellanknappen? Alla dessa data och mer lagras i ett händelseobjekt. För alla webbläsare utom Internet Explorer kan du komma till det här objektet genom att skicka det som en parameter till hanteringsfunktionen. I IE kan du hämta den från den globala variabeln window.event. Det är inte svårt att lösa denna skillnad.

 funktion myFunction (evt) evt = evt || window.event; / * ... * / element.onclick = myFunction;

myFunction tar händelseparametern för alla bra webbläsare, och vi vidarefördelar det till window.event om evt existerar inte. Vi tittar på några av egenskaperna hos händelseobjektet lite senare.

De detta nyckelarbete är viktigt inom händelsehanteringsfunktioner; det hänvisar till det element som tog emot händelsen.

 element.onclick = function () this.style.backgroundColor = 'red'; // samma som element.style.backgroundColor = 'red'; 

Tyvärr, i Internet Explorer, detta hänvisar inte till det uppmätta elementet; det hänvisar till fönsterobjektet. Det är värdelöst, men vi ska titta på ett sätt att åtgärda det lite.


Nivå 2 Händelsehantering

Nivå 2 händelsehantering är W3C-specifikationen för DOM-händelser. Det är ganska enkelt, faktiskt.

 element.addEventListener ('mouseover', myFunction, false);

Metoden heter addeventlistener. Den första parametern är vilken typ av händelse vi vill lyssna på. Vanligtvis är det samma namn som nivån 0 händelser, utan "på" prefixet. Den andra parametern är antingen ett funktionsnamn eller själva funktionen. Den slutliga parametern bestämmer huruvida vi fånga eller inte; Vi diskuterar detta på ett ögonblick.
En av de stora fördelarna med att använda addeventlistener är att vi nu kan tilldela flera händelser för en enda händelse på ett enda element:

 element.addEventListener ('dblclick', logSuccessToConsole, false); element.addEventListener ('dblclick', funktion () console.log ('den här funktionen dövar inte den första.');, false);

För att ta bort dessa hanterare, använd funktionen removeEventListener. Så, för att ta bort funktionen som vi lagt till ovan gör vi det här:

 element.removeEventListener ('dblclick', logSuccessToConsole, false);

Fånga och bubbla

Den sista parametern i addEventListner och removeEventListener är en booleska som definierar om vi brinner händelsen i det fångande eller bubblande scenen. Här är vad det betyder:
När du klickar på ett element klickar du också på var och en av dess förfaderelement.

  
Spara arbete

Om jag klickar på span # spara, Jag har också klickat på div # verktygsfält, div # wrapper, och kropp. Så om något av dessa element har händelselyttare att lyssna på klick, kommer deras respektive funktioner att ringas. Men vilken ordning ska de kallas in? Det är vad den sista parametern bestämmer. I W3C-specifikationen skulle vår klickhändelse börja på kropp och rör ner kedjan tills den når span # spara; det är fångstfasen. Sedan går det från span # spara tillbaka upp till kropp; det är bubblande scenen. Detta är lätt att komma ihåg, eftersom bubblor flyter uppåt. Inte alla händelser bubbla; att Wikipedia-diagrammet kommer att berätta vilka som gör och vilka som inte gör det.

Så om fångstparametern är inställd på Sann, händelsen kommer att utlösas på vägen ner; om det är inställt på falsk, det kommer att utlösas på vägen upp.

Du har kanske märkt att så länge har alla mina exempel ställts in på falska, så händelsen utlöses i bubblingsstadiet. Detta beror på att Internet Explorer inte har en infångningsfas. IEE, händelser bara bubbla.


Internet Explorer Event Model

IE har sitt eget, proprietära sätt att arbeta med händelser. Det använder attachEvent.

 element.attachEvent ('onclick', runThisFunction);

Eftersom IE-händelser bara bubblor behöver du inte den tredje variabeln. De två första är samma som med addeventlistener, med det stora undantaget att IE kräver att "på" prefix för händelsetypen. Naturligtvis kan du tilldela flera händelser av ett slag till ett enskilt element med denna modell. För att ta bort händelser, använd detachEvent.

 element.detachEvent ('onclick', runThisFunction);

Snabbt tillbaka till Bubbling

Vad händer om dina element inte vill att deras föräldrar ska ta reda på deras händelser? Det är inte svårt att dölja. I IE använder du cancelBubble egenskap på händelseobjektet som överförs till den uppringda funktionen. För resten kan du använda stopPropogation metod, även på händelseobjektet.

 e.cancelBubble = true; om (e.stopPropagation) e.stopPropagation (); 

De stopPropogation Metoden kommer också att stoppa händelser i deras spår under fångstfasen.


Ett fåtal egenskaper av händelseobjektet

Jag skulle vara villig att satsa på att de flesta av egenskaperna på händelseobjektet kommer att gå oanvända, men det finns några få de som du tycker är ovärderliga. Låt oss titta på de få.


mål

De mål egendom är det djupaste elementet som klickades. I bilden ovan kan du se att jag klickade på h1 # knapp. I IE heter denna egendom srcElement; att komma till den här egenskapen, gör något så här:

 var target = e.target || e.srcElement;

current

De current egendom är det element som har händelsen just nu. I den skärmdumpen current är div # wrapper. Det betyder att jag klickade på h1 # knapp, men händelseobjektet var loggat till konsolen i den funktion som kallas av div # wrappers händelse lyssnare.

Men det här kommer att kasta dig för en loop: IE har inte en currentTarget-egenskap eller något liknande. Minns det faktum att IE inte ens låter detta lika med det element som utlöser händelsen, betyder det att vi inte har möjlighet att få det element som utlöste händelsen när det bubblar upp (vilket betyder att en efterföljare av elementet klickades). Förvirrad? Kanske ett exempel kan hjälpa till: om i Internet Explorer har vi den här HTML-koden ...

 
Knapp

... och den här händelsen handlar ...

 var par = document.getElementById ("förälder"); parent.attachEvent ('onclick', doSomething);

... då när vi klickar på span # barn och evenemanget bubblor upp till div # förälder, vi har ingen väg från insidan av funktionen göra någonting att komma till elementet (i det här fallet, div # förälder) händelsen utlöstes.

Övriga

Det finns andra egenskaper på händelseobjektet som du finner användbara, beroende på fallet. På ett tangentbordshändelse (keyup, keydown, tangenttryck) får du den nyckelkod och den charCode. Du kommer att kunna berätta om användaren höll alt, shift eller ctrl (cmd) medan du trycker på / klickar. Utforska händelseobjekten av olika händelser och se vad du måste jobba med!


Fixing Events

Vi har stött på två huvudproblem i vår studie av händelser hittills.

  • IE använder en annan modell än de andra webbläsarna.
  • IE har ingen möjlighet att komma åt händelsens förälderobjekt det saknar current egendom och detta objekt refererar till fönstret objektet.

För att lösa dessa problem, låt oss bygga en tvåfunktioner med stöd för cross-browser, en för att koppla upp händelser och en för att ta bort dem.

Vi börjar med addEvent; här är runt en:

 var addEvent = funktion (element, typ, fn) if (element.attachEvent) element.attachEvent ('on' + typ, fn);  else element.addEventListener (typ, fn, false); 

Jag tilldelar en anonym funktion till variabeln addEvent av skäl som du ser på en minut. Allt vi behöver göra är att kontrollera om elementet har attachEvent metod. Om det gör, är vi i IE, och vi använder den metoden. Om inte kan vi använda addeventlistener.

Detta fixar dock inte det faktum att vi inte har tillgång till händelsens föräldraelement i IE. För att göra detta gör vi funktionen ett attribut på elementet; På så sätt uppfattas det som en metod av elementet, och detta kommer att motsvara elementet. Ja, jag vet, lysande, är det inte? Tja, jag kan inte ta någon kredit: den här tanken kommer från ett blogginlägg av John Resig.

 var addEvent = funktion (element, typ, fn) if (element.attachEvent) element ['e' + typ + fn] = fn; element [typ + fn] = funktion () element ['e' + typ + fn] (window.event); element.attachEvent ('på' + typ, element [typ + fn]);  else element.addEventListener (typ, fn, false); 

Den första extra linjen (element ['e' + typ + fn] = fn;) gör funktionen en metod för elementet, så att detta kommer nu att motsvara elementet. Den andra raden är en funktion som kör metoden, som passerar i window.event-objektet. Detta sparar dig extra steg för att leta efter händelseobjektparametern inom din funktion. Slutligen märker vi att vi har ändrat funktionsparametern i attachEvent metod till elementet [typ + fn].

Det löser våra två problem. Vi kan nu använda vår addEvent fungerar i alla webbläsare med så mycket av en liknande erfarenhet som möjligt. Men det finns en liten optimzation som vi skulle vilja göra.

Observera att varje gång vår addEvent funktion kallas, den kontrollerar för attachEvent metod. Det finns ingen anledning till att vi bör kontrollera detta mer än en gång, oavsett hur många händelser vi vill registrera. Vi kan använda ett snyggt trick som gör att vi kan skriva om addEvent.

 var addEvent = funktion (el, typ, fn) if (el.attachEvent) addEvent = funktion (el, typ, fn) el ['e' + typ + fn] = fn; el [typ + fn] = funktion () el ['e' + typ + fn] (window.event); ; el.attachEvent ('on' + typ, el [typ + fn]);  else addEvent = funktion (el, typ, fn) el.addEventListener (typ, fn, false);  addEvent (el, typ, fn); 

Nu, om addEvent Metoden finns, vi skriver om igen addEvent att bara inkludera IE-delarna; Om inte, tar vi bara med de bra webbläsardelarna.
Vår removeEvent funktionen kommer att vara mycket lika:

 var removeEvent = funktion (element, typ, fn) if (element.detachEvent) removeEvent = funktion (element, typ, fn) element.detachEvent ('on' + typ, el [typ + fn]); element [typ + fn] = null;  else removeEvent = funktion (element, typ, fn) element.removeEventListener (typ, fn, false);  removeEvent (element, typ, fn); 

Det är det för våra evenemangsfunktioner för webbläsare. Det finns en sak som jag inte tycker om dem, men: Funktionerna är inte elementets metoder. istället är elementet en parameter. Jag vet att det bara handlar om stil, men jag gillar det

 btn.addEvent ("klicka", gör det här);

bättre än

 addEvent (btn, "click", doThis);

Vi kan göra detta genom att förpacka våra funktioner i detta:

 var E = Element.prototype; E.addEvent = funktion (typ, fn) addEvent (detta, typ, fn);  E.removeEvent = funktion (typ, fn) removeEvent (detta, typ, fn); 

Detta kommer att fungera i alla moderna webbläsare, liksom iE8; IE7 och IE6 strypa på Element. Det beror på din publik ta det eller lämna det!


Evenemangsdelegation

Nu när du vet allt om JavaScript-händelser, skyndar du inte ut och fyller ditt nästa projekt med händelsehörare. Om du har många element som svarar på samma typ av händelse (säg, klicka) är det klokare att dra nytta av att bubbla. Du kan bara lägga till en händelseloggare till ett förfaderelement, till och med kroppen, och använd egenskapen target / srcElement på händelseobjektet för att ta reda på vilket djupare element som klickades. Detta kallas Event delegation. Här är ett oanvändbart exempel:

Här är några HTML:

 
  • Hem
  • portfölj
  • handla om
  • kontakta

Vi kan använda evenemangsdelegation för att hantera klick till var och en li:

 funktionsnavigering (e) var el = e.target || e.srcElement; switch (el.id) case 'home': alert ('gå hem'); ha sönder; fall "portfölj": alert ("kolla in mina produkter"); ha sönder; fallet "om": alert ("alla de äckliga detaljerna"); ha sönder; fallet "kontakt": alert ("Jag smickrade du skulle vilja prata"); ha sönder; standard: alert ('hur kom du hit?');  var nav = document.getElementById ('nav'); addEvent (nav, "klicka", navigering);

Vi använder en switch / case-uppsats för att titta på iden för det djupare klickade elementet. Då gör vi något i enlighet med detta.


Slutsats

Så nu kan du använda JavaScript-händelser med de bästa av dem. Ha kul med det och fråga några frågor i kommentarerna!