Utveckla dina JavaScript-kunskaper! Bli en snabbare, mer produktiv och nöjdare JavaScript-utvecklare genom att bemästra dessa grundläggande och återkommande funktioner i språket.
JavaScript är allestädes närvarande, oavsett om det handlar om backend, frontend eller till och med rymdfarkoster. Det är också ett relativt flexibelt språk med stöd för både funktionella programmeringsmönster och traditionella klasser. Dess likhet med andra ”C-liknande” språk gör det enkelt för utvecklare att byta från andra språk.
För att förbättra dina JS-färdigheter, rekommenderar jag att du lär dig, övar på och behärskar följande grundläggande funktioner i språket. Även om dessa funktioner inte är strikt nödvändiga för att lösa problem, kan de underlätta ditt arbete i vissa fall och minska mängden kod du behöver skriva i andra.
map()
En artikel om viktiga JavaScript-funktioner vore otänkbar utan att nämna map()! Tillsammans med filter() och reduce() utgör map() en mycket viktig trio. Dessa funktioner kommer du att använda om och om igen under din karriär, så de är väl värda att lära sig. Låt oss börja med map().
map() är en av de funktioner som många tycker är svårast att lära sig i JavaScript. Varför? Inte för att den är komplicerad i sig, utan för att dess funktionssätt bygger på koncept från funktionell programmering. Eftersom vi oftast exponeras för objektorienterad programmering i skolan och på arbetet, kan det verka konstigt eller till och med felaktigt för oss.
JavaScript är mer funktionellt än objektorienterat, även om moderna versioner försöker dölja detta faktum. Men det är en annan historia för en annan dag. Okej, så, map()…
map() är en ganska enkel funktion som används för att transformera varje element i en array till något annat, vilket resulterar i en ny array. Hur ett element ska transformeras anges av en annan funktion, som vanligtvis är anonym.
Det är allt! Syntaxen kan kräva lite tillvänjning, men i huvudsak är det detta som map()-funktionen gör. Varför skulle vi använda map()? Det beror på vad vi vill åstadkomma. Låt oss säga att vi har registrerat temperaturen varje dag under den senaste veckan och sparat den i en array. Vi får nu reda på att instrumenten inte var helt exakta och har rapporterat 1,5 grader för låg temperatur.
Vi kan korrigera detta med funktionen map() på följande sätt:
const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23]; const correctedWeeklyReadings = weeklyReadings.map(reading => reading + 1.5); console.log(correctedWeeklyReadings); // ger [ 21.5, 23.5, 22, 20.5, 22.5, 23, 24.5 ]
Ett annat praktiskt exempel är från React-världen, där det är vanligt att skapa listor av DOM-element från arrayer. Det är vanligt att se kod som denna:
export default ({ products }) => { return products.map(product => { return (); }); };{product.name}{product.description}
Här har vi en funktionell React-komponent som får en lista med produkter som inparameter. Från denna lista (array) skapar den en lista med HTML-div-element, vilket i huvudsak konverterar varje produktobjekt till HTML. Det ursprungliga produktobjektet förblir oförändrat.
Man kan argumentera för att map() bara är en mer avancerad for-loop, och det är sant. Men när du gör det, är det din objektorienterade synvinkel som talar. Dessa funktioner och deras logik kommer från funktionell programmering, där enhetlighet, kompakt kod och elegans värdesätts högt. 🙂
filter()
filter() är en mycket användbar funktion som du kommer att använda ofta i många situationer. Som namnet antyder filtrerar denna funktion en array baserat på de regler/logik du anger, och returnerar en ny array som endast innehåller objekt som uppfyller dessa regler.
Låt oss använda vårt väderexempel igen. Anta att vi har en array med maxtemperaturerna för varje dag under den senaste veckan. Nu vill vi veta hur många av dessa dagar som var ”kallare”. Ja, ”kallare” är subjektivt, så låt oss säga att vi söker efter dagar där temperaturen var under 20 grader. Vi kan göra detta med filter()-funktionen på följande sätt:
const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23]; const colderDays = weeklyReadings.filter(dayTemperature => { return dayTemperature < 20; }); console.log("Antalet kallare dagar i veckan var: " + colderDays.length); // 1
Observera att den anonyma funktionen som skickas till filter() måste returnera ett booleskt värde: true eller false. Detta är hur filter() vet om ett objekt ska inkluderas i den filtrerade arrayen eller inte. Du kan skriva valfri komplex logik i denna anonyma funktion. Du kan göra API-anrop, läsa användarinmatningar osv., så länge du i slutändan returnerar ett booleskt värde.
En varning: Jag känner mig skyldig att dela med mig av denna erfarenhet som JavaScript-utvecklare. Oavsett om det beror på slarv eller dåliga grunder, skapar många programmerare små buggar i sin kod när de använder filter(). Låt oss skriva om föregående kod så att den innehåller en bugg:
const weeklyReadings = [20, 22, 20.5, 19, 21, 21.5, 23]; const colderDays = weeklyReadings.filter(dayTemperature => { return dayTemperature < 20; }); if(colderDays) { console.log("Ja, det var kallare dagar förra veckan"); } else { console.log("Nej, det var inga kallare dagar"); }
Ser du något? Bra om du gjorde det! If-villkoret i slutet kontrollerar colderDays, som faktiskt är en array! Det är förvånansvärt hur många gånger folk gör detta misstag när de skyndar sig att hålla deadlines eller kodar i dåligt humör (av olika anledningar). Problemet med detta villkor är att JavaScript är ett konstigt och inkonsekvent språk på många sätt, och ”sanningen” i saker är en av dem. Även om [] == true returnerar false, vilket får dig att tro att koden ovan inte är felaktig, är verkligheten att inom ett if-villkor tolkas [] som sant! Med andra ord kommer koden vi skrev aldrig att säga att det inte fanns några kallare dagar förra veckan.
Lösningen är mycket enkel, som visas i koden före ovanstående kod. Vi kontrollerar colderDays.length, som garanterat ger oss ett heltal (noll eller högre) och därmed kan användas i logiska jämförelser. Observera att filter() alltid kommer att returnera en array, tom eller icke-tom, så vi kan lita på det och skriva våra logiska jämförelser med tillförsikt.
Det här blev en längre utflykt än jag hade planerat, men sådana här buggar är värda tusentals ord, med stora och små bokstäver om det behövs. Jag hoppas att du slipper drabbas av det här och sparar dig själv hundratals timmar av felsökning! 🙂
reduce()
Av alla funktioner i den här artikeln, och i det vanliga JavaScript-biblioteket, är reduce() en av de främsta kandidaterna till titeln ”förvirrande och konstigt”. Även om den här funktionen är mycket viktig och resulterar i elegant kod i många situationer, undviks den av de flesta JavaScript-utvecklare, som föredrar att skriva mer utförlig kod i stället.
Anledningen är att – och jag ska vara ärlig här! – reduce() är svår att förstå, både konceptuellt och hur den fungerar. När du läser dess beskrivning kan du ha läst om den flera gånger och fortfarande tvivla på dig själv om du läst den fel; och när du ser den i aktion och försöker visualisera hur den fungerar, snurrar din hjärna! 🤭
Men bli inte rädd. Funktionen reduce() är inte alls lika komplex och skrämmande som, säg, B+ träd och deras algoritmer. Det är bara det att den här typen av logik sällan dyker upp under den genomsnittliga programmerarens vardagliga arbete.
Så efter att ha skrämt slag på dig och sedan omedelbart sagt att du inte ska oroa dig, vill jag äntligen visa dig vad den här funktionen är och varför vi kan behöva den.
Som namnet antyder används reduce() för att reducera något. Det som reduceras är en array, och det som den givna arrayen reduceras till är ett enda värde (tal, sträng, funktion, objekt, vad som helst). Här är ett enklare sätt att uttrycka det – reduce() omvandlar en array till ett enda värde. Observera att returvärdet från reduce() inte är en array, vilket är fallet med map() och filter(). Att förstå så mycket är redan halva slaget. 🙂
Det är ganska uppenbart att om vi ska transformera (reducera) en array måste vi tillhandahålla den nödvändiga logiken, och utifrån din erfarenhet som JS-utvecklare har du nog redan gissat att vi gör det med en funktion. Denna funktion är det vi kallar för en reduceringsfunktion, som utgör det första argumentet till reduce(). Det andra argumentet är ett startvärde, som ett tal, en sträng osv. (Jag ska förklara om en stund vad detta ”startvärde” är).
Utifrån det vi har förstått hittills kan vi säga att ett anrop till reduce() ser ut så här: array.reduce(reducerFunction, startingValue). Låt oss nu ta itu med kärnan i det hela: reduceringsfunktionen. Som redan fastställts är det reduceringsfunktionen som talar om för reduce() hur man ska omvandla arrayen till ett enda värde. Den behöver två argument: en variabel för att fungera som en ackumulator (var inte orolig, jag ska förklara den här delen också), och en variabel för att lagra det aktuella värdet.
Jag vet, jag vet… det var mycket terminologi för en enda funktion som inte ens är obligatorisk i JavaScript. 😝😝 Och det är därför folk flyr från reduce(). Men om du lär dig det steg för steg kommer du inte bara att förstå det utan också uppskatta det när du blir en bättre utvecklare.
Okej, tillbaka till ämnet. ”Startvärdet” som skickas till reduce() är… ja, startvärdet för den beräkning du vill göra. Om du till exempel ska utföra multiplikation i reduceringsfunktionen är det lämpligt att börja med 1; för addition kan du börja med 0 och så vidare.
Låt oss nu titta på signaturen för reduceringsfunktionen. En reduceringsfunktion som skickas till reduce() har följande form: reducerFunction(accumulator, currentValue). ”Ackumulator” är bara ett fint namn för variabeln som samlar in och lagrar resultatet av beräkningen; det är precis som att använda en variabel som heter total för att summera alla element i en array med något i stil med total += arr[i]. Det är precis så här reduceringsfunktionen i reduce() fungerar: ackumulatorn sätts initialt till det startvärde du anger, och sedan gås elementen i arrayen igenom ett efter ett, beräkningen utförs och resultatet lagras i ackumulatorn, och så vidare…
Så vad är detta ”aktuella värde” i en reduceringsfunktion? Det är samma sak som du skulle tänka dig om jag bad dig gå igenom en array: du skulle använda en variabel som börjar på index noll och flytta den framåt ett steg i taget. Om jag bad dig att stanna abrupt, skulle du befinna dig på ett av elementen i arrayen, eller hur? Det är det vi menar med aktuellt värde: det är värdet på variabeln som används för att representera det arrayelement som för närvarande behandlas (tänk på att loopa genom en array om det hjälper).
Med allt detta sagt är det dags att se ett enkelt exempel och se hur all denna jargong sätts ihop i ett riktigt anrop till reduce(). Låt oss säga att vi har en array som innehåller de första n naturliga talen (1, 2, 3… n) och vi vill hitta fakulteten för n. Vi vet att för att hitta n! behöver vi bara multiplicera allt, vilket leder oss till följande implementering:
const numbers = [1, 2, 3, 4, 5]; const factorial = numbers.reduce((acc, item) => acc * item, 1); console.log(factorial); // 120
Det händer mycket i dessa tre rader kod, så låt oss packa upp det steg för steg utifrån den (mycket långa) diskussion vi har haft hittills. Som är uppenbart är siffror den array som innehåller alla tal vi vill multiplicera. Titta sedan på anropet numbers.reduce(), som säger att startvärdet för acc ska vara 1 (eftersom det inte påverkar eller förstör någon multiplikation). Kontrollera sedan reduceringsfunktionens kropp, `(acc, item) => acc * item, som helt enkelt säger att returvärdet för varje iteration över arrayen ska vara objektet multiplicerat med det som redan finns i ackumulatorn. Iterationen och den faktiska lagringen av multiplikationen explicit i ackumulatorn är vad som händer bakom kulisserna, och det är en av de största anledningarna till att reduce() är en sådan stötesten för JavaScript-utvecklare.
Varför använda reduce()?
Det är en riktigt bra fråga, och ärligt talat har jag inget säkert svar. Allt som reduce() gör kan göras med loopar, forEach() osv. Men dessa tekniker resulterar i mycket mer kod, vilket gör den svårläst, särskilt om du har bråttom. Sedan finns det oron för oföränderlighet: med reduce() och liknande funktioner kan du vara säker på att din ursprungliga data inte har förändrats; detta eliminerar i sig en hel del typer av buggar, särskilt i distribuerade applikationer.
Slutligen är reduce() mycket mer flexibel, i den meningen att ackumulatorn kan vara ett objekt, en array eller till och med en funktion om det behövs. Detsamma gäller för startvärdet och andra delar av funktionsanropet – nästan vad som helst kan komma in, och nästan vad som helst kan komma ut, så det finns en extrem flexibilitet i att utforma återanvändbar kod.
Om du fortfarande inte är övertygad är det helt okej; JavaScript-communityn är skarpt delad när det gäller ”kompaktheten”, ”elegansen” och ”kraften” hos reduce(), så det är okej om du inte använder den. 🙂 Men se till att du tittar på några snygga exempel innan du bestämmer dig för att helt ignorera reduce().
some()
Låt oss säga att du har en array med objekt, där varje objekt representerar en person. Du vill veta om det finns personer i gruppen som är över 35 år. Observera att det inte finns något behov av att räkna hur många sådana personer det finns, och ännu mindre att hämta en lista över dem. Det vi säger här motsvarar ”en eller flera” eller ”minst en”.
Hur gör du det här?
Ja, du kan skapa en flaggvariabel och loopa genom arrayen för att lösa problemet på följande sätt:
const persons = [ { name: 'Person 1', age: 32 }, { name: 'Person 2', age: 40 }, ]; let foundOver35 = false; for (let i = 0; i < persons.length; i ++) { if(persons[i].age > 35) { foundOver35 = true; break; } } if(foundOver35) { console.log("Japp, det finns några personer här!") }
Problemet? Koden är för C-liknande eller Java-liknande, tycker jag. ”Verbose” är ett annat ord som dyker upp. Erfarna JS-utvecklare kanske tänker ”ful”, ”hemsk” osv. 😝 Och med rätta, skulle jag vilja påstå. Ett sätt att förbättra den här koden är att använda något i stil med map(), men inte ens det är en riktigt bra lösning.
Det visar sig att vi har en riktigt praktisk funktion som heter some() som redan finns tillgänglig i språket. Den här funktionen fungerar med arrayer och accepterar en anpassad ”filtreringsfunktion” som returnerar ett booleskt värde true eller false. I huvudsak gör den det vi har försökt göra de senaste minuterna, fast mycket mer kortfattat och elegant. Så här kan vi använda den:
const persons = [ { name: 'Person 1', age: 32 }, { name: 'Person 2', age: 40 }, ]; if(persons.some(person => { return person.age > 35 })) { console.log("Hittade några personer!") }
Samma indata, samma resultat som tidigare, men lägg märke till den enorma minskningen av kod! Lägg också märke till hur drastiskt den kognitiva bördan minskar eftersom vi inte längre behöver analysera koden rad för rad som om vi vore tolken! Koden läser nu nästan som ett naturligt språk.
every()
Precis som some() har vi en annan användbar funktion som heter every(). Som du kanske gissar vid det här laget returnerar även denna ett booleskt värde beroende på om alla objekt i arrayen klarar det givna testet. Naturligtvis anges testet i en anonym funktion i de flesta fall. Jag ska bespara dig besväret med hur en naiv version av koden kan se ut, så här är hur every() används:
const entries = [ { id: 1 }, { id: 2 }, { id: 3 }, ]; if(entries.every(entry => { return Number.isInteger(entry.id) && entry.id > 0; })) { console.log("Alla poster har ett giltigt id") }
Som är uppenbart kontrollerar koden alla objekt i arrayen för en giltig id-egenskap. Definitionen av ”giltig” beror på kontexten, men i den här koden ansåg jag icke-negativa heltal. Återigen ser vi hur enkel och elegant koden är att läsa, vilket är det enda syftet med denna (och liknande) funktion(er).
includes()
Hur kontrollerar du om delsträngar och arrayelement finns? Tja, om du är som jag, använder du snabbt indexOf() och sedan slår du upp dokumentationen för att se vilka möjliga returvärden som finns. Det är krångligt och returvärdena är svåra att komma ihåg (snabbt – vad betyder en process som returnerar 2 till operativsystemet?).
Men det finns ett bra alternativ som vi kan använda oss av: includes(). Användningen är lika enkel som namnet, och den resulterande koden är extremt trevlig. Tänk på att matchningen som görs av includes() är skiftlägeskänslig, men jag antar att det är vad vi alla intuitivt förväntar oss i alla fall. Och nu är det dags för lite kod!
const numbers = [1, 2, 3, 4, 5]; console.log(numbers.includes(4)); const name = "Ankush"; console.log(name.includes('ank')); // false, eftersom första bokstaven är i små bokstäver console.log(name.includes('Ank')); // true, som förväntat
Men förvänta dig inte för mycket av den här enkla metoden:
const user = {a: 10, b: 20}; console.log(user.includes('a')); // kraschar, eftersom objekt inte har en "includes"-metod
Den kan inte titta in i objekt eftersom den helt enkelt inte är definierad för objekt. Men hey, vi vet att den fungerar på arrayer, så vi kanske kan använda oss av något knep här… 🤔.
const persons = [{name: 'Phil'}, {name: 'Jane'}]; persons.includes({name: 'Phil'});
Så vad händer när du kör den här koden? Det kraschar inte, men resultatet är också en besvikelse: false. 😫😫 Det här har att göra med objekt, pekare och hur JavaScript ser på och hanterar minne, vilket är en egen värld. Om du vill dyka djupare kan du göra det (kanske börja här), men jag slutar här.
Vi kan få koden ovan att fungera om vi skriver om den på följande sätt, men vid det här laget börjar det kännas lite krystat, enligt mig:
const phil = {name: 'Phil'}; const persons = [phil, {name: 'Jane'}]; persons.includes(phil); // true
Ändå visar det att vi kan använda include() med objekt, så jag antar att det inte är en total katastrof. 😄
slice()
Anta att vi har en sträng, och jag ber dig att returnera en del av den som börjar med ”r” och slutar med ”z” (de faktiska tecknen är inte viktiga). Hur skulle du lösa det? Kanske skulle du skapa en ny sträng och använda den för att lagra alla nödvändiga tecken och returnera dem. Eller, om du är som de flesta programmerare, skulle du ge mig två arrayindex: ett som anger början av delsträngen och det andra som markerar slutet.
Båda dessa metoder är bra, men det finns ett koncept som kallas slicing, som erbjuder en bra lösning i sådana situationer. Som tur är finns det ingen konstig teori att följa; slicing betyder precis det som det låter som – att skapa en mindre sträng/array från den givna, ungefär som att skära skivor av frukt. Låt oss se vad jag menar med ett enkelt exempel:
const headline = "Och i kvällens special, gästen som vi alla har väntat på!"; const startIndex = headline.indexOf('gäst'); const endIndex = headline.indexOf('väntat'); const newHeadline = headline.slice(startIndex, endIndex); console.log(newHeadline); // gästen som vi alla har
När vi använder slice() anger vi två index för JavaScript – det ena där vi vill börja slicingen och det andra där vi vill att den ska sluta. Haken med slice() är att slutindexet inte ingår i slutresultatet, vilket är anledningen till att ordet ”väntar” saknas i den nya rubriken i koden ovan.
Koncept som slicing är vanligare i andra språk, särskilt Python. Om du frågar de utvecklarna kommer de att säga att de inte kan föreställa sig livet utan den här funktionen, och med rätta, eftersom språket erbjuder en väldigt trevlig syntax för slicing.
Att slica är smidigt och extremt praktiskt, och det finns ingen anledning att inte använda det. Det är inte heller bara syntaxsocker med prestandastraff, eftersom det skapar ytliga kopior av den ursprungliga arrayen/strängen. För JavaScript-utvecklare rekommenderar jag starkt att du bekantar dig med slice() och lägger till den i din verktygslåda!
splice()
Metoden splice() låter som en kusin till slice(), och på sätt och vis är det sant. Båda skapar nya arrayer/strängar från de ursprungliga, med en liten men viktig skillnad – splice() tar bort, ändrar eller lägger till element men modifierar den ursprungliga arrayen. Den här ”förstörelsen” av den ursprungliga arrayen kan orsaka stora problem om du inte är försiktig eller förstår djupa kopior och referenser. Jag undrar varför utvecklarna inte använde samma tillvägagångssätt som för slice() och lämnade den ursprungliga arrayen oförändrad, men jag antar att vi kan vara mer förlåtande mot ett språk som skapades på bara tio dagar.
Trots mina klagomål ska vi titta på hur splice() fungerar. Jag ska visa ett exempel där vi tar bort några element från en array, eftersom det är den vanligaste användningen av den här metoden. Jag kommer också att avstå från att ge exempel på hur man lägger till och infogar, eftersom det är enkelt att slå upp och inte svårt att förstå.
const items = ['ägg', 'mjölk', 'ost', 'bröd', 'smör']; items.splice(2, 1); console.log(items); // [ 'ägg', 'mjölk', 'bröd', 'smör' ]
Anropet till splice() ovan säger: börja vid index 2 (dvs. den tredje platsen) i arrayen och ta bort ett objekt. I den givna arrayen är ”ost” det tredje objektet, så det tas bort från arrayen och arrayen med objekt kortas ned, som förväntat. Förresten, de borttagna objekten returneras av splice() som en array, så om vi hade velat kunde vi ha fångat ”ost” i en variabel.
Enligt min erfarenhet har indexOf() och splice() stor synergi – vi hittar indexet för ett objekt och tar sedan bort det från den givna arrayen. Observera dock att det inte alltid är den mest effektiva metoden, och ofta är det mycket snabbare att använda ett objekt (motsvarande en hashmap) som nyckel.
shift()
shift() är en bekväm metod som används för att ta bort det första elementet i en array. Observera att samma sak kan göras med splice(), men shift() är lite lättare att komma ihåg och intuitivt när allt du behöver göra är att ta bort det första elementet.
const items = ['ägg', 'mjölk', 'ost', 'bröd', 'smör']; items.shift() console.log(items); // [ 'mjölk', 'ost', 'bröd', 'smör' ]
unshift()
Precis som shift() tar bort det första elementet från en array lägger unshift() till ett nytt element i början av arrayen. Användningen är lika enkel och kortfattad:
const items = ['ägg', 'mjölk']; items.unshift('bröd') console.log(items); // [ 'bröd', 'ägg', 'mjölk' ]
Jag kan inte låta bli att varna dem som är nya i spelet: till skillnad från de populära metoderna push() och pop() är shift() och unshift() extremt ineffektiva (på grund av hur de underliggande algoritmerna fungerar). Så om du använder stora arrayer (säg 2000+ objekt) kan för många av dessa funktionsanrop sakta ner din applikation.
fill()
Ibland behöver du ändra flera objekt till ett enda värde eller till och med ”återställa” hela arrayen, så att säga. I dessa situationer räddar fill() dig från loopar och off-by-one-fel. Den kan användas för att ersätta en del eller hela arrayen med det angivna värdet. Låt oss se några exempel:
const heights = [1, 2, 4, 5, 6, 7, 1, 1]; heights.fill(0); console.log(heights); // [0, 0, 0, 0, 0, 0, 0, 0] const heights2 = [1, 2, 4, 5, 6, 7, 1, 1]; heights2.fill(0, 4); console.log(heights2); // [1, 2, 4, 5, 0, 0, 0, 0]
Andra funktioner som är värda att nämna
Även om listan ovan innehåller det som de flesta JavaScript-utvecklare stöter på och använder i sina karriärer, är den inte på något sätt komplett. Det finns många fler mindre men användbara funktioner (metoder) i JavaScript att det inte går att täcka dem alla i en enda artikel. Några som jag kommer att tänka på är:
- reverse()
- sort()
- entries()
- fill()
- find()
- flat()
Jag rekommenderar att du åtminstone slår upp dessa, så att du vet att dessa praktiska funktioner finns.
Sammanfattning
JavaScript är ett stort språk, trots det lilla antalet grundläggande begrepp som behöver läras in. De många funktionerna (metoderna) som finns tillgängliga för oss utgör huvuddelen av denna storlek. Men eftersom JavaScript är ett sekundärt språk för de flesta utvecklare, gräver vi inte tillräckligt djupt och missar många användbara funktioner som det erbjuder. Detsamma gäller för koncepten för funktionell programmering, men det är en annan historia för en annan dag! 😅
Närhelst du kan, lägg lite tid på att utforska språket (och om möjligt kända bibliotek som Lodash). Även några minuter som ägnas åt detta kommer att ge enorma produktivitetsvinster och mycket renare och kompaktare kod.