jQuery-antimönster och bästa praxis

För länge sedan, i en galax långt, långt borta, var JavaScript ett hatat språk. Faktum är att "hatade" är en underdrift; JavaScript var ett föraktat språk. Till följd av detta behandlade utvecklare generellt det som sådant, bara tippade tårna i JavaScript-vattnet när de behövde spruta lite känsla i sina applikationer. Trots att det finns en hel del bra på JavaScript-språket, på grund av utbredd okunnighet, tog några tid att lära sig ordentligt. Istället, som vissa av er kanske kommer ihåg, innebar standard JavaScript-användning en betydande mängd kopiering och klistra in.

"Låt inte lära dig vad koden gör, eller om det följer bästa praxis, bara klistra in det!" - Värsta råd någonsin

Eftersom uppkomsten av jQuery reignited intresse för JavaScript-språk, är mycket av informationen på webben lite sketchy.

Ironiskt nog visar det sig att mycket av det som utvecklingssamhället hatade, hade väldigt lite att göra med JavaScript-språket i sig. Nej, den verkliga hoten under masken var DOM, eller "Document Object Model", som speciellt vid den tiden var oerhört inkonsekvent från webbläsaren till webbläsaren. "Visst kan det fungera i Firefox, men hur är det med IE8? Okej, det kan fungera i IE8, men hur är det med IE7?" Listan gick oförtröttligt!

Lyckligtvis började JavaScript-gemenskapen ungefär fem år sedan se en otrolig förändring desto bättre, eftersom bibliotek som jQuery introducerades för allmänheten. Inte bara gav dessa bibliotek en uttrycksfull syntax som var särskilt tilltalande för webbdesigners, men de lyckades också jämföra spelupplevelsen genom att tömma lösningarna för de olika webbläsarens quirks i dess API. trigger $ .ajax och låt jQuery göra den svåra delen. Snabba framåt till idag, och JavaScript-communityen är mer levande än någonsin - till stor del på grund av jQuery-revolutionen.

Eftersom uppkomsten av jQuery reignited intresse för JavaScript-språk, är mycket av informationen på webben lite sketchy. Det här är mindre på grund av författarnas okunnighet, och mer ett resultat av det faktum att vi alla lärde oss. Det tar tid för bästa praxis att dyka upp.

Lyckligtvis har samhället mognat oerhört sedan dess. Innan vi dyker in i några av dessa bästa praxis, låt oss först avslöja några dåliga råd som har cirkulerat runt på webben.


Använd inte jQuery

Problemet med tips som detta är att de tar upp tanken på preoptimering till ett extremt.

På samma sätt som Ruby on Rails var många utvecklare första introduktion till JavaScript genom jQuery. Detta leder till en gemensam cykel: lär dig jQuery, bli kär, gräva i vanilj JavaScript och nivå upp.

Medan det säkert inte är något fel med denna cykel, banade det vägen för otaliga artiklar, som rekommenderade användarna inte använd jQuery i olika situationer på grund av "prestationsproblem". Det är inte ovanligt att läsa att det är bättre att använda vanilj för slingor, över $ .each. Eller, på något eller annat vis, kan du ha läst att det är bästa praxis att använda document.getElementsByClassName över jQuery's Sizzle-motor, eftersom det är snabbare.

Problemet med tips som detta är att de tar ideen om preoptimering till extrema och inte tar hänsyn till olika inkonsekvenser i webbläsaren - de saker som jQuery fixade för oss! Att köra ett test och observera besparingar på några millisekunder över tusenvis av repetitioner är inte anledning att överge jQuery och dess eleganta syntax. Din tid är mycket bättre investerade tweaking delar av din ansökan som faktiskt kommer att göra en skillnad, till exempel storleken på dina bilder.


Flera jQuery-objekt

Det här andra antimönstret var återigen resultatet av samhället (inklusive dina på ett och samma sätt) inte fullt ut förstå vad som ägde rum under jQuery-huven. Som sådan kom du sannolikt över (eller skrev själv) kod, som inslagna ett element i jQuery-objektet otaliga gånger inom en funktion.

$ ('button.confirm'). på ('klicka', funktion () // Gör det en gång $ ('modal'). modal (); // Och en gång till $ ('.modal'). addClass ('aktiv'); // Och återigen till stor del $ ('modal'). css (...););

Medan den här koden kanske i början verkar vara ofarlig (och sanningsenligt är, i det stora systemet), följer vi den dåliga övningen att skapa flera instanser av jQuery-objektet. Varje gång vi hänvisar till $ ( ". Modal), ett nytt jQuery-objekt genereras. Är det smart?

Tänk på DOM som en pool: varje gång du ringer $ ( ". Modal), jQuery dykar in i poolen och jagar ner tillhörande mynt (eller element). När du upprepade gånger frågar DOM för samma väljare kastar du i grunden dessa mynt tillbaka i vattnet, bara för att hoppa in och hitta dem igen!

Kör alltid väljare om du tänker använda dem mer än en gång. Det tidigare kodbiten kan refactoreras till:

$ ('knapp.bekräfta'). på ('klick', funktion () $ ('modal') .modal () .addClass ('aktiv') .css (...););

Alternativt använd "caching".

$ ('key.confirm'). ; modal.css (...););

Med denna teknik hoppar jQuery in i DOM-poolen totalt en gång, snarare än tre.


Väljarens prestanda

För mycket uppmärksamhet ges till väljare prestanda.

Medan inte så allestädes närvarande, för länge sedan, blev webben bombarderad av otaliga artiklar om optimering av väljarsprestation i jQuery. Till exempel är det bättre att använda $ ('div p') eller $ (Div). Hitta ( 'p')?

Klar för sanningen? Det spelar ingen roll så mycket. Det är verkligen en bra idé att ha en grundläggande förståelse för hur jQuery's Sizzle-motorn analyserar dina väljarsökningar från höger till vänster (vilket betyder att det är bättre att vara mer specifikt i slutet av din väljare snarare än början). Och ju ju ju mer specifika du kan vara desto bättre. Klart, $ (A.button) är bättre för prestanda än $ ( ". Knapp), på grund av det faktum att jQuery med den förra kan begränsa sökningen till endast ankarelementen på sidan, snarare än alla element.

Utöver det är emellertid för mycket uppmärksamhet åt väljarens prestanda. När du är i tvivel, lita på att jQuery-teamet består av de finaste JavaScript-utvecklarna i branschen. Om det finns en prestationsökning som ska uppnås i biblioteket kommer de att ha upptäckt det. Och, om inte dem, kommer en av medlemmarna att lämna in en dröjningsförfrågan.

Med detta i åtanke ska du vara medveten om dina väljare, men oroa dig inte för mycket med prestationseffekter, såvida du inte kan verbalisera varför det är nödvändigt.


Återkalla Helvetet

jQuery har uppmuntrat utbredd användning av återuppringningsfunktioner, vilket säkert kan ge en bra bekvämlighet. Snarare än att förklara en funktion, använd helt enkelt en återuppringningsfunktion. Till exempel:

$ ('a.external'). på ('klick', funktion () // denna återuppringningsfunktion utlöses // när extern klickas);

Du har verkligen skrivit mycket kod som ser ut så här; Jag vet att jag har! När de används sparsamt fungerar anonyma återuppringningsfunktioner som användbara bekvämligheter. Gnidret kommer ner i raden när vi går in ... callback hell (trigger thunderbolt sound)!

Återkalla helvete är när din kod indrager sig många gånger, eftersom du fortsätter att nesta återuppringningsfunktioner.

Tänk på följande ganska vanliga kod nedan:

$ (this) .fadeOut (400, funktion () $ .ajax (// ... framgång: funktion (data) anchor.fadeIn (400, funktion () // du har just angett callback hell););););

Som en grundläggande tommelfingerregel är ju mer inryckt din kod, desto mer sannolikt finns det en kodlukt. Eller, ännu bättre, fråga dig själv, ser min kod ut som Mighty Ducks Flying V?

När refactoring kod som detta är nyckeln är att fråga dig själv, "Hur kunde det här testas?" Inom denna till synes enkla bit kod är en händelseloggare bunden till en länk, elementet tappar ut, ett AJAX-samtal utförs. Efter framgång kommer elementet att dyka tillbaka, förmodligen kommer de resulterande dataerna att läggas till någonstans. Det är säkert mycket att testa!

Skulle det inte vara bättre att dela upp koden i mer hanterbara och testbara bitar? Säkert. Även om följande kan optimeras ytterligare, kan ett första steg för att förbättra den här koden vara:

var updatePage = funktion (el, data) // lägger till hämtad data till DOM; var hämta = funktion (ajaxOptions) ajaxOptions = ajaxOptions || // url: ... // dataType: ... framgång: updatePage; returnera $ .ajax (ajaxOptions); ; $ ('a.data'). på ('klick', funktion () $ (detta) .fadeOut (400, hämta););

Ännu bättre, om du har en mängd olika åtgärder att utlösa, innehåller relevanta metoder inom ett objekt.

Tänk på hur i en snabbmat restaurang, som McDonalds, är varje arbetare ansvarig för en uppgift. Joe gör pommes frites, Karen registrerar kunder, och Mike Grillar hamburgare. Om alla tre anställda gjorde allt, skulle det innebära ett antal underhållsproblem. När förändringar behöver genomföras måste vi träffas med varje person för att diskutera dem. Om vi ​​till exempel behåller Joe uteslutande fokuserad på pommes frites, bör vi behöva justera instruktionerna för att förbereda pommes frites, vi behöver bara prata med Joe och ingen annan. Du borde ha en liknande inställning till din kod; varje funktion är ansvarig för en uppgift.

I koden ovan är hämta funktionen utlöser bara ett AJAX-samtal till den angivna webbadressen. De updatePage funktionen accepterar vissa data och lägger till den i DOM. Nu, om vi vill testa en av dessa funktioner för att se till att det fungerar som förväntat, till exempel updatePage metod kan vi mocka dataobjektet och skicka det till funktionen. Lätt!


Uppfinna hjulet på nytt

Det är viktigt att komma ihåg att jQuery-ekosystemet har mognat kraftigt under de senaste åren. Chansen är att om någon har behov av en viss komponent, har någon annan byggt den. Visst, fortsätt bygga plugins för att öka din förståelse för jQuery-biblioteket (faktiskt skriver vi en i den här artikeln), men för verklig användning hänvisar du till eventuella befintliga plugins innan du återuppfinner hjulet.

Till exempel behöver du en datumväljare för ett formulär? Spara dig själv och jobba i stället med det community-driven - och högprövade - jQuery UI-biblioteket.

När du hänvisar till det nödvändiga jQuery UI-biblioteket och tillhörande formatark, är processen med att lägga till en datumväljare till en ingång lika enkelt som att göra:

 

Eller hur är det med ett dragspel? Visst kan du skriva den funktionen själv, eller istället, dra nytta av jQuery UI.

Skapa bara den nödvändiga markeringen för ditt projekt.

Kapitel 1

Några texter.

kapitel 2

Några texter.

Kapitel 3

Några texter.

Avsnitt 4

Några texter.

Sedan, auto förvandla den till ett dragspel.

$ (funktion () $ ("# dragspel"). dragspel (););

Vad händer om du kan skapa flikar på trettio sekunder?

Skapa markeringen:

  • Om oss
  • Vårt uppdrag
  • Komma i kontakt

Om oss text.

Vår uppdragstext.

Kom i kontakttext.

Och aktivera plugin.

$ (funktion () $ ("# flikar"). flikar (););

Gjort! Det behöver inte ens någon anmärkningsvärd förståelse för JavaScript.


Pluginutveckling

Låt oss nu gräva in några bästa metoder för att bygga jQuery-plugins, som du är skyldig att göra vid någon tidpunkt i din utvecklings karriär.

Vi använder en relativt enkel Meddelandebox plugin som demo för vår inlärning. Känn dig fri att arbeta med; i själva verket, snälla gör det!

Uppgiften: implementera nödvändig funktionalitet för att visa dialogrutor, med hjälp av syntaxen, $ .message ("SÄKER TILL ANVÄNDAREN"). På det här sättet, till exempel innan du raderar en post permanent, kan vi begära att användaren bekräftar åtgärden, snarare än att använda sig av oflexibilitet och ful varna lådor.


Steg 1

Det första steget är att räkna ut hur man "aktiverar" $ .message. I stället för att förlänga jQuerys prototyp, för det här pluginets krav behöver vi bara bifoga en metod till jQuery namespace.

(funktion ($) $ .message = funktion (text) console.log (text);;) (jQuery);

Det är lika lätt som det; fortsätt, prova det! När du ringer $ .message ('Här är mitt meddelande'), den här strängen bör loggas till webbläsarens konsol (Shift + Command + i, i Google Chrome).


Steg 2

Medan det inte finns tillräckligt med utrymme för att täcka processen med att testa plugin, är detta ett viktigt steg som du bör undersöka. Det finns en fantastisk känsla av trygghet som inträffar, när refactoring testad kod.

Till exempel, när du använde jQuerys testpaket, QUnit, kunde vi testa koden från steg 1 genom att skriva:

 modulen ("jQuery.message", test ("finns på jQuery namespace", 1, funktion () ok $. meddelande, "meddelande metod ska existera");););

De ok funktion, tillgänglig via QUnit, hävdar helt enkelt att det första argumentet är ett truthy värde. Om meddelande Metoden existerar inte på jQuery namespace då falsk kommer att returneras, i vilket fall testet misslyckas.

Efter det testdrivna utvecklingsmönstret skulle denna kod vara det första steget. När du har observerat testet misslyckas, skulle nästa steg vara att lägga till meddelande metod, följaktligen.

Medan för korthetens skull, kommer denna artikel inte att dyka längre in i processen med testdriven utveckling i JavaScript, du uppmanas att hänvisa till GitHub repo för detta projekt för att granska alla test för plugin: https: // github .com / JeffreyWay / messageBox / blob / Master / test / MessageBox_test.js


Steg 3

en meddelande Metod som gör ingenting är inte till hjälp för någon! Låt oss ta det angivna meddelandet och visa det för användaren. Men snarare än att inbädda en stor glob av kod i $ .message metod, använder vi istället bara funktionen för att inställa och initiera en Meddelande objekt.

(funktion) (text) this.text = text; return this;;; $ .message = funktion (text) // Behöver polyfil för IE8- - returnera Object.create (Message). initiera (text);;) (jQuery);

Inte bara gör detta tillvägagångssätt igen Meddelande objekt mer testbar, men det är också en renare teknik, som bland annat skyddar igen callback helvete. Tänk på detta Meddelande objekt som representation av en enda meddelandebox.


Steg 4

Om Meddelande representerar en enda meddelandebox, vad blir HTML för en? Låt oss skapa en div med en klass av meddelandebox, och göra den tillgänglig för Meddelande Exempel via en el fast egendom.

var Message = initialisera: funktion (text) this.el = $ ('
', ' class ':' message-box ',' style ':' display: none '); this.text = text; returnera detta; ;

Nu har föremålet en omedelbar hänvisning till omslaget div för meddelandeboxen. För att få tillgång till det kunde vi göra:

var msg = Object.create (Message). initiera (); // [
â € <
â € "console.log (msg.el);

Kom ihåg att vi nu har ett HTML-fragment, men det har ännu inte infogats i DOM. Det betyder att vi inte behöver oroa oss för några onödiga reflows, när vi lägger till innehåll på div.


Steg 5

Nästa steg är att ta den medföljande meddelandesträngen och sätt in den i div. Det är lätt!

initiera: funktion (text) // ... this.el.html (this.text);  // [
"Här är ett viktigt budskap"
â € <]

Det är dock osannolikt att vi vill infoga texten direkt i div. Mer realistiskt kommer meddelandeboxen att ha en mall. Medan vi skulle kunna låta användaren av plugin skapa en mall och referera den, låt oss hålla saker enkelt och begränsa mallen till Meddelande objekt.

 var Message = mall: funktion (text, knappar) return ['

'+ text +'

','
', knappar'
' ].Ansluta sig("); //… ;

I situationer, när du inte har något annat val än att hysa HTML i din JavaScript, är ett populärt tillvägagångssätt att lagra HTML-fragmenten som objekt i en array och sedan ansluta dem till en HTML-sträng.

I det här utgåvan läggs nu meddelandets text i ett stycke med en klass av meddelande-box-text. Vi har också satt en plats för knapparna, som vi ska genomföra inom kort.

Nu den initialisera Metoden kan uppdateras till:

initiera: funktion (text) // ... this.el.html (this.template (text, knappar)); 

När vi utlöser bygger vi strukturen för meddelandeboxen:

Här är ett viktigt budskap.


Steg 6

För att meddelandefältet ska vara så flexibelt som möjligt måste användaren av pluginet ha möjlighet att valfritt ange vilka knappar som ska presenteras för användaren - t.ex. "Okay", "Cancel" "Ja , "etc. Vi borde kunna lägga till följande kod:

$ .message ('Är du säker?', knappar: ['Ja', 'Avbryt']);

... och skapa en meddelandebox med två knappar.

För att implementera denna funktionalitet, uppdatera först $ .message definition.

$ .message = funktion (text, inställningar) var msg = Object.create (Message); msg.initialize (text, inställningar); returnera msg; ;

Nu den inställningar objektet skickas vidare till initialisera metod. Låt oss uppdatera det.

initiera: funktion (text, inställningar) this.el = $ ('
', ' class ':' message-box ',' style ':' display: none '); this.text = text; this.settings = inställningar this.el.html (this.template (text, knappar));

Inte dåligt, men vad händer om plugin-användaren inte anger några inställningar? Tänk alltid på de olika sätt som din plugin kan användas.


Steg 7

Vi antar att användaren av pluginet kommer att beskriva vilka knappar som ska presenteras, men för att vara säker är det viktigt att tillhandahålla en uppsättning standardinställningar, vilket är en bra metod när du skapar plugins.

$ .message = funktion (text, inställningar) var msg = Object.create (Message); msg.initialize (text, inställningar); returnera msg; ; $ .message.defaults = ikon: 'info', knappar: ['Okay'], återuppringning: null;

Med detta tillvägagångssätt, om plugin-användaren behöver ändra standardinställningarna behöver han bara uppdatera $ .message.defaults, efter behov. Kom ihåg: göm aldrig standardinställningarna från användaren. Gör dem tillgängliga för "utsidan".

Här har vi satt några standardvärden: ikonen, knapparna och en återuppringningsfunktion, som ska utlösas, när användaren har klickat på en av knapparna i meddelandefältet.

jQuery erbjuder ett användbart sätt att åsidosätta standardalternativen för ett plugin (eller något objekt, verkligen) via dess förlänga metod.

initiera: funktion (text, knappar) // ... this.settings = $ .extend (, $ .message.defaults, inställningar); 

Med denna modifiering, this.settings kommer nu att vara lika med ett nytt objekt. Om plugin-användaren anger några inställningar, kommer de att överstyra pluginens defaults objekt.


Steg 8

Om vi ​​avser att lägga till en anpassad ikon i meddelandefältet, beroende på åtgärden, är det nödvändigt att lägga till en CSS-klass i elementet och tillåta användaren att tillämpa en bakgrundsbild, i enlighet med detta.

Inom initialisera metod, lägg till:

this.el.addClass ('message-box-' + this.settings.icon);

Om ingen ikon anges i inställningar objekt, standard, info, är använd: .meddelande-box-info. Nu kan vi erbjuda en mängd olika CSS-klasser (utöver omfattningen av denna handledning), innehållande olika ikoner för meddelandeboxen. Här är några exempel för att komma igång.

.meddelande-box-info bakgrund: url (sökväg / till / info / icon.png) no-repeat;  .meddelande-box-varning bakgrund: url (sökväg / till / varning / icon.png) no-repeat; 

Idealiskt, som en del av ditt MessageBox-plugin, vill du inkludera ett externt stilark som innehåller grundläggande styling för meddelandefältet, dessa klasser och en handfull ikoner.


Steg 9

Insticksprogrammet accepterar nu en rad knappar som ska tillämpas på mallen, men vi har ännu inte skrivit funktionaliteten för att göra den informationen användbar. Det första steget är att ta en rad knappvärden och översätta det till de nödvändiga HTML-inmatningarna. Skapa en ny metod på Meddelande föremål för att hantera denna uppgift.

createButtons: funktion (knappar) 

jQuery.map är en användbar metod som tillämpar en funktion för varje objekt inom en array och returnerar en ny array med de ändringar som tillämpas. Detta är perfekt för våra behov. För varje objekt i knapparna array, till exempel ['Ja Nej'], Vi vill ersätta texten med en HTML-ingång, med värdet inställt, i enlighet med detta.

createButtons: funktion (knappar) return $ .map (knappar, funktion (knapp) returnera ''; ).Ansluta sig("); 

Uppdatera sedan initialisera metod för att ringa denna nya metod.

initiera: funktion (text, inställningar) this.el = $ ('
', ' class ':' message-box ',' style ':' display: none '); this.text = text; this.settings = $ .extend (, $ .message.defaults, inställningar); var knappar = this.createButtons (this.settings.buttons); this.el.html (this.template (text, knappar)); returnera detta;

Steg 10

Vilken bra är en knapp, om inget händer när användaren klickar på det? Ett bra ställe att lagra alla evenemangslyttare för en vy ligger inom en speciell evenemang metod på det associerade objektet, så här:

initiera: funktion () // ... this.el.html (this.template (text, knappar)); this.events (); , händelser: funktion () var själv = detta; this (en) (själv, $ (detta) .val ());); 

Denna kod är lite mer komplex, eftersom användaren av pluginet måste kunna utlösa sin egen återuppringningsfunktion, när en knapp klickas på meddelandefältet. Koden bestämmer helt enkelt om a ring tillbaka funktionen registrerades och, om så är fallet, utlöser den och skickas via vald knapps värde. Tänk dig att en meddelandebox innehåller två knappar: "Acceptera" och "Avbryt". Användaren av plugin måste ha ett sätt att fånga upp den klickade knappens värde och svara i enlighet därmed.

Lägg märke till var vi ringer self.close ()? Den metod som ännu inte har skapats är ansvarig för en sak: stängning och borttagning av meddelandefältet från DOM.

stäng: funktion () this.el.animate (top: 0, opacity: 'hide', 150, funktion () $ (detta) .remove ();); 

För att lägga till lite känsla, döljer vi meddelandeboxen, under 150 millisekunder, ut ur lådan och övergår den uppåt.


Steg 11

Funktionaliteten har implementerats! Det sista steget är att presentera meddelandeboxen för användaren. Vi lägger till en sista show metod på Meddelande objekt som kommer att infoga meddelandeboxen i DOM och placera den.

visa: funktion () this.el.appendTo ('body'). animera (top: $ (fönster) .height () / 2 - this.el.outerHeight () / 2, opacity: 'show', 300); 

Det tar bara en enkel beräkning att placera rutan lodrätt i mitten av webbläsarfönstret. Med det på plats är pluggen komplett!

$ .message = funktion (text, inställningar) var msg = Object.create (Message). initiera (text, inställningar); msg.show (); returnera msg; ;

Steg 12

För att använda din nya plugin, ring bara $ .Message (), och passera genom ett meddelande och alla tillämpliga inställningar, som så:

$ .message ('Raden har uppdaterats.');

Eller för att begära bekräftelse för vissa destruktiva åtgärder:

$ .message ('Vill du verkligen radera denna post?', knappar: ['Ja', 'Avbryt'], ikon: "alert", återuppringning: funktion (buttonText) if (buttonText === 'Ja ') // fortsätt och radera rekord);

Slutsatser

För att lära dig mer om jQuery-utveckling uppmanas du att hänvisa till min kurs på denna sida, "30 dagar att lära dig jQuery."

Under byggandet av detta prov Meddelandebox plugin har en rad bästa metoder uppstått, till exempel att undvika callback helvete, skriva testbar kod, vilket gör standardalternativen tillgängliga för plugin-användaren och säkerställer att varje metod är ansvarig för exakt en uppgift.

Medan man verkligen skulle kunna uppnå samma effekt genom att bädda in otaliga återuppringningsfunktioner inom $ .message, Att göra det är sällan en bra idé, och anses även som ett motmönster.

Kom ihåg de tre nycklarna till underhållsbar kod och flexibla jQuery-plugins och skript:

  1. Kan jag testa detta? Om inte, refactor och dela upp koden i bitar.
  2. Har jag erbjudit möjligheten att åsidosätta mina standardinställningar?
  3. Jag följer dåliga rutiner eller antaganden?

För att lära dig mer om jQuery-utveckling uppmanas du att hänvisa till min screencast-kurs på denna sida, "30 dagar för att lära dig jQuery."