JavaScript är över femton år gammalt; Ändå är språket fortfarande missförstått av vad som kanske är majoriteten av utvecklare och designers som använder språket. En av de mest kraftfulla, men missförstådda, aspekterna av JavaScript är funktioner. Medan fruktansvärt viktigt för JavaScript kan deras missbruk införa ineffektivitet och hindra programmets prestanda.
I webbets spädbarn var prestanda inte så viktigt.
I webbets spädbarn var prestanda inte så viktigt. Från 56K (eller sämre) uppringningsanslutningar till en slutanvändares 133MHz Pentium-dator med 8MB RAM, förväntades webben vara långsam (även om det inte hindrade alla från att klaga på det). Det var därför JavaScript skapades till att börja med, för att avlasta enkel bearbetning, till exempel formvalidering, till webbläsaren, vilket gör vissa uppgifter enklare och snabbare för slutanvändaren. I stället för att fylla i ett formulär, klicka på Skicka och vänta minst trettio sekunder att få veta att du har angett felaktiga uppgifter i ett fält, JavaScript-aktiverade webbförfattare att validera din inmatning och varna dig för eventuella fel innan formulärets inlämning.
Snabb fram till idag. Slutanvändare tycker om multi-core och multi-GHz-datorer, ett överflöd av RAM och snabba anslutningshastigheter. JavaScript är inte längre förskjutet till validering av menyformat, men det kan hantera stora mängder data, ändra vilken del av en sida som helst i flygningen, skicka och ta emot data från servern och lägga till interaktivitet på en annars statisk sida - allt i namnet för att förbättra användarens erfarenhet. Det är ett mönster som är ganska välkänt i hela datorindustrin. En växande mängd systemresurser gör det möjligt för utvecklare att skriva mer sofistikerade och resursberoende operativsystem och programvara. Men även med denna rikliga och allt större mängd resurser måste utvecklare vara medvetna om hur mycket resurser deras app förbrukar, särskilt på webben.
Dagens JavaScript-motorer är ljusår före motorerna för tio år sedan, men de optimerar inte allt. Vad de inte optimerar lämnas till utvecklare.
Det finns också en helt ny uppsättning av webaktiverade enheter, smarta telefoner och surfplattor, som körs på en begränsad uppsättning resurser. Deras trimmade operativsystem och appar är säkert en träff, men de stora operativsystemen för mobila operativsystem (och till och med stationära OS-leverantörer) letar efter webbteknologi som valfri utvecklingsplattform och driver JavaScript-utvecklare för att säkerställa att deras kod är effektiv och effektiv..
En dålig tillämpning kommer att skräpa en bra upplevelse.
Viktigast är att användarens erfarenhet beror på bra prestanda. Vackra och naturliga användargränssnitt lägger visserligen till användarens upplevelse, men en dålig utövande applikation kommer skräp en bra upplevelse. Om användarna inte vill använda din programvara, vad är meningen med att skriva den? Så det är absolut nödvändigt att, på den här tiden och åldern av webbcentrerad utveckling, skriver JavaScript-utvecklare den bästa koden som är möjlig.
Så vad har allt detta att göra med funktioner?
Där du definierar dina funktioner påverkar din applikations prestanda.
Det finns många JavaScript-antimönster, men en funktion som är involverad har blivit lite populär, särskilt i publiken som strävar efter att tvinga JavaScript att efterlikna funktioner på andra språk (funktioner som sekretess). Det är häckningsfunktioner i andra funktioner, och om det görs felaktigt kan det påverka din ansökan negativt.
Det är viktigt att notera att detta antimönster inte gäller alla instanser av kapslade funktioner, men det definieras typiskt av två egenskaper. Först är skapandet av funktionen i fråga vanligtvis uppskjuten, vilket innebär att den nestade funktionen inte skapas av JavaScript-motorn vid laddningstid. Det i sig är inte en dålig sak, men det är den andra egenskapen som hindrar prestanda: den nestade funktionen skapas upprepade gånger på grund av upprepade samtal till den yttre funktionen. Så medan det kan vara lätt att säga att "alla kapslade funktioner är dåliga", så är det inte så, och du kommer att kunna identifiera problematiska kapslade funktioner och fixa dem för att påskynda din ansökan.
Det första exemplet på detta antimönster är att nesta en funktion i en normal funktion. Här är ett förenklat exempel:
funktion foo (a, b) funktionsfält () returnera a + b; Returfältet (); foo (1, 2);
Du får inte skriva den här exakta koden, men det är viktigt att känna igen mönstret. En yttre funktion, foo ()
, innehåller en inre funktion, bar()
, och uppmanar den inre funktionen att göra arbete. Många utvecklare glömmer att funktioner är värden i JavaScript. När du förklarar en funktion i din kod skapar JavaScript-motorn ett motsvarande funktionsobjekt, ett värde som kan tilldelas en variabel eller skickas till en annan funktion. Åtgärden att skapa ett funktionsobjekt liknar det av någon annan typ av värde; JavaScript-motorn skapar inte den tills den behöver. Så när det gäller ovanstående kod skapar inte JavaScript-motorn det inre bar()
funktion till foo ()
exekverar. När foo ()
utgångar, bar()
Funktionsobjektet förstörs.
Faktumet att foo ()
har ett namn innebär att det kommer att kallas flera gånger i hela applikationen. Medan en körning av foo ()
skulle anses vara OK, efterföljande samtal orsakar onödigt arbete för JavaScript-motorn eftersom det måste återskapa en bar()
funktion objekt för varje foo ()
avrättning. Så, om du ringer foo ()
100 gånger i en applikation måste JavaScript-motorn skapa och förstöra 100 bar()
funktionsobjekt. Stor affär, eller hur? Motorn måste skapa andra lokala variabler i en funktion varje gång den heter, så varför bryr sig om funktioner?
Till skillnad från andra typer av värden förändras funktioner vanligen inte; en funktion skapas för att utföra en specifik uppgift. Så det ger ingen mening att slösa bort CPU-cykler som återskapar ett något statiskt värde om och om igen.
Helst är det bar()
Funktionsobjektet i det här exemplet bör endast skapas en gång, och det är lätt att uppnå - även om det naturligtvis krävs mer omfattande funktioner som kräver omfattande refaktorer. Tanken är att flytta bar()
deklaration utanför foo ()
så att funktionsobjektet endast skapas en gång, så här:
funktion foo (a, b) returfältet (a, b); funktionsfältet (a, b) return a + b; foo (1, 2);
Observera att den nya bar()
funktionen är inte precis som den var inuti foo ()
. För den gamla bar()
funktionen använde en
och b
parametrar i foo ()
, den nya versionen behövde refactoring för att acceptera dessa argument för att kunna utföra sitt arbete.
Beroende på webbläsaren är den här optimerade koden var som helst från 10% till 99% snabbare än den kapslade versionen. Du kan visa och köra testet själv på jsperf.com/nested-named-functions. Tänk på enkelheten i det här exemplet. En 10% (vid den lägsta änden av prestandaspektrummet) verkar inte som mycket, men det skulle vara högre, eftersom mer kapslade och komplexa funktioner är inblandade.
För att kanske förväxla problemet, pakka in den här koden i en anonym, självförverkande funktion, så här:
(funktion () funktion foo (a, b) returfältet (a, b); funktionsfältet (a, b) return a + b; foo (1,2);
Omslagskod i en anonym funktion är ett vanligt mönster, och vid första anblicken kan det tyckas att den här koden replikerar den ovannämnda prestandafrågan genom att pakka in den optimerade koden i en anonym funktion. Även om det finns en liten prestation träffad genom att utföra den anonyma funktionen, är den här koden helt acceptabel. Den självförverkande funktionen tjänar endast att innehålla och skydda foo ()
och bar()
funktioner, men ännu viktigare, den anonyma funktionen exekverar endast en gång -med den inre foo ()
och bar()
funktioner skapas bara en gång. Det finns emellertid några fall där anonyma funktioner är lika (eller mer) problematiska som namngivna funktioner.
När det gäller detta ämnesområde har anonyma funktioner potential att vara farligare än namngivna funktioner.
Det är inte anonymiteten för den funktion som är farlig, men det är hur utvecklare använder dem. Det är ganska vanligt att använda anonyma funktioner när man konfigurerar händelsehanterare, återuppringningsfunktioner eller iteratorfunktioner. Till exempel tilldelar följande kod a klick
händelse lyssnare på dokumentet:
document.addEventListener ("click", funktion (evt) alert ("Du klickade på sidan."););
Här skickas en anonym funktion till addeventlistener ()
metod för att ansluta klick
händelse på dokumentet så utför funktionen varje gång användaren klickar någonstans på sidan. För att visa en annan gemensam användning av anonyma funktioner, överväga det här exemplet som använder jQuery-biblioteket för att välja allt element i dokumentet och iterera över dem med
varje()
metod:
$ ("a"). varje (funktion (index) this.style.color = "red";);
I den här koden överfördes den anonyma funktionen till jQuery-objektets varje()
Metoden exekverar för varje element som finns i dokumentet. Till skillnad från namngivna funktioner, där de antas att upprepas kallas, är det upprepade utförandet av ett stort antal anonyma funktioner ganska explicit. Det är avgörande för prestanda att de är effektiva och optimerade. Ta en titt på följande (ännu en gång översimplifierad) jQuery-plugin:
$ .fn.myPlugin = funktion (alternativ) return this.each (funktion () var $ this = $ (detta); funktionsbyteColor () $ this.css (color: options.color); changeColor ();); ;
Denna kod definierar en extremt enkel plugin som heter myPlugin
; Det är så enkelt att många vanliga plugin-egenskaper saknas. Normalt är plugindefinitioner inslagna i egna exekverande anonyma funktioner, och vanligtvis tillhandahålls standardvärden för alternativ för att säkerställa att giltiga data är tillgängliga att använda. Dessa saker har tagits bort för tydlighetens skull.
Det här pluginets syfte är att ändra de valda elementens färg till vad som anges i alternativ
objekt skickat till myPlugin ()
metod. Det gör det genom att ge en anonym funktion till varje()
iterator, vilket gör den här funktionen exekverad för varje element i jQuery-objektet. Inne i den anonyma funktionen kallas en inre funktion ändra färg()
gör det faktiska arbetet med att ändra elementets färg. Som skrivet är den här koden ineffektiv eftersom du gissade den, den ändra färg()
funktion definieras inuti iterationsfunktionen? gör att JavaScript-motorn återskapas ändra färg()
med varje iteration.
Att göra denna kod effektivare är ganska enkel och följer samma mönster som tidigare: refactor the ändra färg()
funktion som ska definieras utanför eventuella funktioner och tillåta den att få den information som behövs för att utföra sitt arbete. I detta fall, ändra färg()
behöver jQuery-objektet och det nya färgvärdet. Den förbättrade koden ser så här ut:
funktion changeColor ($ obj, färg) $ obj.css (color: color); $ .fn.myPlugin = funktion (alternativ) return this.each (funktion () var $ this = $ (detta); changeColor ($ this, options.color);); ;
Intressant, denna optimerade kod ökar prestanda med en mycket mindre marginal än foo ()
och bar()
Exempelvis med Chrome som leder paketet med en prestandaförbättring på 15% (jsperf.com/function-nesting-with-jquery-plugin). Sanningen är att få tillgång till DOM och använda jQuery's API lägga till egen träff för prestanda, speciellt jQuery s varje()
, vilket är notoriskt långsam jämfört med javascript inbyggda loopar. Men som tidigare, kom ihåg enkelheten i detta exempel. De mer kapslade funktionerna, ju större prestationsvinst från optimering.
En annan variant av detta anti-mönster är nestningsfunktioner inom konstruktörer, såsom visas nedan:
funktion Person (förnamn, efternamn) this.firstName = firstName; this.lastName = lastName; this.getFullName = function () return this.firstName + "" + this.lastName; ; var jeremy = ny person ("Jeremy", "McPeak"), jeffrey = ny person ("Jeffrey", "Way");
Denna kod definierar en konstruktörfunktion som heter Person()
, och det representerar (om det inte var uppenbart) en person. Den accepterar argument som innehåller en persons för- och efternamn och lagrar dessa värden i förnamn
och efternamn
egenskaper respektive Konstruktören skapar också en metod som heter getFullName ()
; det sammanfattar förnamn
och efternamn
egenskaper och returnerar det resulterande strängvärdet.
När du skapar något objekt i JavaScript lagras objektet i minnet
Det här mönstret har blivit ganska vanligt i dagens JavaScript-community eftersom det kan emulera sekretess, en funktion som JavaScript inte är för närvarande utformad för (notera att privatlivet inte finns i det ovanstående exemplet, du kommer att se på det senare). Men när man använder det här mönstret, skapar utvecklare ineffektivitet, inte bara i exekveringstid, men i minnesanvändning. När du skapar något objekt i JavaScript lagras objektet i minnet. Den förblir i minnet tills alla referenser till den är antingen inställda på null
eller är utom räckhåll. I fallet med jeremy
objekt i ovanstående kod, funktionen tilldelad till getFullName
lagras vanligtvis i minnet så länge som jeremy
objektet är i minnet. När jeffrey
objekt skapas, skapas ett nytt funktionsobjekt och tilldelas jeffrey
's getFullName
medlem, och det förbrukar också minne så länge som jeffrey
är i minnet. Problemet här är det jeremy.getFullName
är ett annat funktionsobjekt än jeffrey.getFullName
(jeremy.getFullName === jeffrey.getFullName
resulterar i falsk
; köra den här koden på http://jsfiddle.net/k9uRN/). De båda har samma beteende, men de är två helt olika funktionsobjekt (och därmed förbrukar varje minne). För tydligheten, ta en titt på Figur 1:
Här ser du jeremy
och jeffrey
objekt, som alla har sina egna getFullName ()
metod. Så, var och en Person
objekt skapat har sin egen unika getFullName ()
metod - var och en förbrukar sin egen bit av minne. Tänk dig att skapa 100 Person
objekt: om varje getFullName ()
Metoden förbrukar 4KB minne, sedan 100 Person
objekt skulle förbrukas åtminstone 400 kb minne. Det kan lägga upp, men det kan minskas drastiskt med hjälp av prototyp
objekt.
Som tidigare nämnts är funktioner objekt i JavaScript. Alla funktionsobjekt har a prototyp
egendom, men det är bara användbart för konstruktörsfunktioner. Kort sagt, prototyp
egendom är ganska bokstavligen en prototyp för att skapa objekt; vad som definieras på en konstruktorfunktions prototyp delas bland alla objekt som skapas av den konstruktörsfunktionen.
Tyvärr är prototyper inte tillräckligt stressade i JavaScript-utbildning.
Tyvärr är prototyper inte tillräckligt stressade i JavaScript-utbildning, men de är absolut nödvändiga för JavaScript eftersom det bygger på och byggs med prototyper-det är ett prototypiskt språk. Även om du aldrig skrev ordet prototyp
I din kod används de bakom kulisserna. Till exempel, varje inbyggd strängbaserad metod, som dela()
, substr ()
, eller byta ut()
, definieras på Sträng()
s prototyp. Prototyper är så viktiga för JavaScript-språket att om du inte omfamnar JavaScript-prototypen, skriver du ineffektiv kod. Tänk på ovanstående genomförande av Person
datatyp: skapa en Person
objekt kräver att JavaScript-motorn gör mer arbete och tilldelar mer minne.
Så hur kan man använda prototyp
egendom gör denna kod effektivare? Tja, först ta en titt på den refactored koden:
funktion Person (förnamn, efternamn) this.firstName = firstName; this.lastName = lastName; Person.prototype.getFullName = function () returnera detta.första namnet + "" + this.lastName; ; var jeremy = ny person ("Jeremy", "McPeak"), jeffrey = ny person ("Jeffrey", "Way");
Här, den getFullName ()
metoddefinition flyttas ut ur konstruktören och på prototypen. Denna enkla förändring har följande effekter:
getFullName ()
Metoden skapas bara en gång och delas bland alla Person
objekt (jeremy.getFullName === jeffrey.getFullName
resulterar i Sann
; köra den här koden på http://jsfiddle.net/Pfkua/). På grund av detta, varje Person
objekt använder mindre minne. Hänvisa till Figur 1 och notera hur varje objekt har sitt eget getFullName ()
metod. Nu när getFullName ()
definieras på prototypen ändras objektdiagrammet och visas i figur 2:
De jeremy
och jeffrey
objekt har inte längre egna getFullName ()
metod, men JavaScript-motorn kommer att hitta den på Person()
s prototyp. I äldre JavaScript-motorer kan processen med att hitta en metod på prototypen medföra en prestationsfrekvens, men inte så i dagens JavaScript-motorer. Hastigheten vid vilken moderna motorer hittar prototyperna är extremt snabb.
Men hur är det med integritet? Det här antimönstret var trots allt fött ur ett uppfattat behov av privata objektmedlemmar. Om du inte känner till mönstret, ta en titt på följande kod:
funktionen Foo (paramOne) var thisIsPrivate = paramOne; this.bar = function () returnera thisIsPrivate; ; var foo = ny Foo ("Hej, Sekretess!"); alert (foo.bar ()); // varningar "Hej, Sekretess!"
Denna kod definierar en konstruktörfunktion som heter Foo ()
, och den har en parameter som heter paramOne
. Värdet gick till Foo ()
lagras i en lokal variabel som heter thisIsPrivate
. Anteckna det thisIsPrivate
är en variabel, inte en egenskap; så det är otillgängligt utanför Foo ()
. Det finns också en metod som definieras inom konstruktören, och den heter bar()
. Därför att bar()
definieras inom Foo ()
, den har tillgång till thisIsPrivate
variabel. Så när du skapar en foo
objekt och samtal bar()
, värdet tilldelat till thisIsPrivate
returneras.
Värdet tilldelat till thisIsPrivate
bevaras. Det kan inte nås utanför Foo ()
, och sålunda är den skyddad från yttre modifiering. Det är bra, eller hur? Jo ja och nej. Det är förståeligt varför vissa utvecklare vill efterlikna sekretess i JavaScript: du kan se till att ett objekts data är säkrad från utsidan av manipulering. Men samtidigt introducerar du ineffektivitet för din kod genom att inte använda prototypen.
Så igen, vad sägs om integritet? Jo det är enkelt: gör det inte. Språket stöder för närvarande inte officiellt privata objektmedlemmar, även om det kan förändras i en framtida översyn av språket. I stället för att använda stängningar för att skapa privata medlemmar, är konventionen att beteckna "privata medlemmar" att förordna identifieraren med ett understreck (dvs: _thisIsPrivate
). Följande kod skriver om det föregående exemplet med hjälp av konventionen:
funktion Foo (paramOne) this._thisIsPrivate = paramOne; Foo.prototype.bar = function () returnera this._thisIsPrivate; ; var foo = ny Foo ("Hej, konvention för att ange sekretess!"); alert (foo.bar ()); // varningar "Hej, konvent för att beteckna sekretess!"
Nej, det är inte privat, men understrykningskonventionen säger i princip "rör mig inte". Till dess att JavaScript helt stödjer privata egenskaper och metoder, skulle du inte ha mer effektiv och effektiv kod än integritet? Det rätta svaret är: ja!
Där du definierar funktioner i din kod påverkar din applikations prestanda; Kom ihåg det när du skriver din kod. Nacka inte funktioner inom en ofta kallad funktion. Att göra så förlorar processorns cykler. När det gäller konstruktörfunktioner, omfamna prototypen; Underlåtenhet att göra det resulterar i ineffektiv kod. Trots allt skriver utvecklare programvara för användarna att använda, och en applikations prestanda är lika viktig för användarens upplevelse som användargränssnittet.