10 viktiga Lodash-funktioner för JavaScript-utvecklare

By rik

Lodash: Ett oumbärligt verktyg för JavaScript-utvecklare

För de som arbetar med JavaScript är Lodash inget nytt. Det är ett välkänt bibliotek. Men dess storlek kan ibland kännas överväldigande. Nu är det slut med det!

Lodash, Lodash, Lodash… Var ska man ens börja? 🤔

Det var en tid då JavaScript-miljön var i sin linda. Man kan jämföra den med vilda västern eller en djungel, där mycket hände men få svar fanns på utvecklarnas vardagliga problem och produktivitetsutmaningar.

Sedan kom Lodash och det kändes som en befriande våg. Lodash löste många problem, från enkla behov som sortering till komplexa datatransformationer, och var fullpackat (kanske till och med överbelastat!) med funktioner som gjorde livet enklare för JS-utvecklare.

Hej Lodash!

Och var befinner sig Lodash idag? Tja, det har fortfarande alla de fantastiska funktioner som det hade från början, och lite till, men det verkar ha tappat sin status i JavaScript-gemenskapen. Varför? Jag har några teorier:

  • Vissa funktioner i Lodash-biblioteket var (och är fortfarande) långsamma när de tillämpades på stora datamängder. Även om detta sällan skulle påverka de flesta projekt, så gav de inflytelserika utvecklarna från de få projekt som berördes Lodash dålig publicitet. Den negativa effekten spred sig sedan till övriga utvecklare.
  • Det finns en tendens inom JS-miljön (och kanske även bland Golang-utvecklare) där högmod är vanligare än nödvändigt. Att förlita sig på något som Lodash betraktas som dumt, och förslag på sådana lösningar blir nedröstade på forum som StackOverflow (”Va?! Använda ett helt bibliotek för det här? Jag kan kombinera filter() med reduce() för att göra samma sak i en egen funktion!”).
  • Lodash är gammalt. Åtminstone med JS-mått mätt. Det lanserades 2012, vilket innebär att det nästan är tio år gammalt. API:et har varit stabilt och få spännande nyheter har lagts till varje år (eftersom det helt enkelt inte behövs). Detta skapar tristess hos den genomsnittliga, överentusiastiska JS-utvecklaren.

Jag anser att det är en stor förlust för våra JavaScript-projekt att inte använda Lodash. Det har visat sig erbjuda pålitliga och eleganta lösningar på vardagliga problem som vi stöter på i arbetet. Att använda det gör vår kod mer lättläst och enklare att underhålla.

Med det sagt, låt oss granska några vanliga (och mindre vanliga!) Lodash-funktioner och se hur otroligt användbart och bra det här biblioteket är.

Kloning – på djupet!

Eftersom objekt i JavaScript skickas som referenser, får utvecklare problem när de vill klona något i tron att den nya datamängden ska vara annorlunda.

let people = [
  {
    name: 'Arnold',
    specialization: 'C++',
  },
  {
    name: 'Phil',
    specialization: 'Python',
  },
  {
    name: 'Percy',
    specialization: 'JS',
  },
];

// Hitta personer som kodar i C++
let folksDoingCpp = people.filter((person) => person.specialization == 'C++');

// Konvertera dem till JS!
for (person of folksDoingCpp) {
  person.specialization = 'JS';
}

console.log(folksDoingCpp);
// [ { name: 'Arnold', specialization: 'JS' } ]

console.log(people);
/*
[
  { name: 'Arnold', specialization: 'JS' },
  { name: 'Phil', specialization: 'Python' },
  { name: 'Percy', specialization: 'JS' }
]
*/

Observera hur den ursprungliga ”people”-arrayen, i all oskyldighet och trots goda avsikter, ändrades i processen (Arnolds specialisering ändrades från C++ till JS). Detta är ett hårt slag mot dataintegriteten! Vi behöver ett sätt att skapa en riktig (djup) kopia av den ursprungliga arrayen.

Låt mig presentera en lösning!

Du kanske hävdar att det här är ett ”dumt” sätt att koda i JS. Men verkligheten är lite mer komplex. Visst, vi har den fantastiska destruktureringsoperatorn, men alla som har försökt destrukturera komplexa objekt och arrayer vet hur smärtsamt det kan vara. Dessutom finns det idén att använda serialisering och deserialisering (kanske med JSON) för att skapa en djup kopia, men det gör bara koden rörigare för den som läser den.

Se hur elegant och koncis lösningen blir när Lodash används:

const _ = require('lodash');

let people = [
  {
    name: 'Arnold',
    specialization: 'C++',
  },
  {
    name: 'Phil',
    specialization: 'Python',
  },
  {
    name: 'Percy',
    specialization: 'JS',
  },
];

let peopleCopy = _.cloneDeep(people);

// Hitta personer som kodar i C++
let folksDoingCpp = peopleCopy.filter(
  (person) => person.specialization == 'C++'
);

// Konvertera dem till JS!
for (person of folksDoingCpp) {
  person.specialization = 'JS';
}

console.log(folksDoingCpp);
// [ { name: 'Arnold', specialization: 'JS' } ]

console.log(people);
/*
[
  { name: 'Arnold', specialization: 'C++' },
  { name: 'Phil', specialization: 'Python' },
  { name: 'Percy', specialization: 'JS' }
]
*/

Observera hur ”people”-arrayen är oförändrad efter djupkloningen (Arnold är fortfarande specialist på C++ i det här fallet). Och framförallt, koden är lätt att förstå.

Ta bort dubbletter från en array

Att ta bort dubbletter från en array låter som en klassisk intervjufråga eller ett whiteboard-problem (kom ihåg, när du inte vet vad du ska göra, lägg till en hashmap!). Du kan förstås alltid skriva en egen funktion, men vad händer om du stöter på flera olika situationer där du behöver ta bort dubbletter? Du kan skriva flera egna funktioner (och riskera att införa små buggar), eller så kan du helt enkelt använda Lodash!

Vårt första exempel med unika arrayer är ganska enkelt, men det visar ändå snabbheten och tillförlitligheten som Lodash bidrar med. Tänk dig att du skulle göra det här genom att skriva all logik själv!

const _ = require('lodash');

const userIds = [12, 13, 14, 12, 5, 34, 11, 12];
const uniqueUserIds = _.uniq(userIds);
console.log(uniqueUserIds);
// [ 12, 13, 14, 5, 34, 11 ]

Observera att den slutliga arrayen inte är sorterad, vilket i det här fallet inte är ett problem. Men låt oss nu tänka oss ett mer komplicerat scenario: vi har en mängd användare som vi hämtat någonstans, men vi vill se till att den bara innehåller unika användare. Det är enkelt med Lodash!

const _ = require('lodash');

const users = [
  { id: 10, name: 'Phil', age: 32 },
  { id: 8, name: 'Jason', age: 44 },
  { id: 11, name: 'Rye', age: 28 },
  { id: 10, name: 'Phil', age: 32 },
];

const uniqueUsers = _.uniqBy(users, 'id');
console.log(uniqueUsers);
/*
[
  { id: 10, name: 'Phil', age: 32 },
  { id: 8, name: 'Jason', age: 44 },
  { id: 11, name: 'Rye', age: 28 }
]
*/

I det här exemplet använde vi metoden uniqBy() för att ange att vi vill att objekten ska vara unika baserat på egenskapen ”id”. Med en enda rad åstadkom vi något som hade kunnat ta 10-20 rader och introducerat mer risk för buggar!

Det finns många fler metoder för att göra saker unika i Lodash, och jag uppmanar dig att ta en titt på dokumentationen.

Skillnaden mellan två arrayer

Union, skillnad och så vidare, kan låta som begrepp som man helst lämnar bakom sig i tråkiga gymnasieföreläsningar om mängdteori. Men de dyker upp oftare än man tror i den dagliga praktiken. Det är vanligt att man har en lista och vill slå ihop den med en annan lista, eller att man vill hitta de element som är unika för den jämfört med en annan lista. I sådana situationer är skillnadsfunktionen perfekt.

Låt oss utforska skillnaden genom att ta ett enkelt scenario: du har en lista över alla användar-ID:n i systemet, samt en lista över de vars konton är aktiva. Hur hittar du de inaktiva ID:n? Enkelt, eller hur?

const _ = require('lodash');

const allUserIds = [1, 3, 4, 2, 10, 22, 11, 8];
const activeUserIds = [1, 4, 22, 11, 8];

const inactiveUserIds = _.difference(allUserIds, activeUserIds);
console.log(inactiveUserIds);
// [ 3, 2, 10 ]

Och vad händer om du, som oftast är fallet i en mer realistisk miljö, behöver arbeta med en array med objekt istället för vanliga primitiva datatyper? Tja, Lodash har en utmärkt differenceBy()-metod för det!

const allUsers = [
  { id: 1, name: 'Phil' },
  { id: 2, name: 'John' },
  { id: 3, name: 'Rogg' },
];
const activeUsers = [
  { id: 1, name: 'Phil' },
  { id: 2, name: 'John' },
];
const inactiveUsers = _.differenceBy(allUsers, activeUsers, 'id');
console.log(inactiveUsers);
// [ { id: 3, name: 'Rogg' } ]

Visst är det snyggt?!

Precis som med skillnad, så finns det andra metoder i Lodash för vanliga mängdoperationer: union, korsning osv.

Platta ut arrayer

Behovet av att platta ut arrayer dyker upp ganska ofta. Ett exempel är att du har fått ett API-svar och behöver använda en kombination av map() och filter() på en komplex lista med kapslade objekt/arrayer för att hämta ut exempelvis användar-ID:n, och då har du kvar arrayer av arrayer. Här är ett kodexempel som visar situationen:

const orderData = {
  internal: [
    { userId: 1, date: '2021-09-09', amount: 230.0, type: 'prepaid' },
    { userId: 2, date: '2021-07-07', amount: 130.0, type: 'prepaid' },
  ],
  external: [
    { userId: 3, date: '2021-08-08', amount: 30.0, type: 'postpaid' },
    { userId: 4, date: '2021-06-06', amount: 330.0, type: 'postpaid' },
  ],
};

// Hitta användar-ID:n som lagt beställningar med efterbetalning (internt eller externt)
const postpaidUserIds = [];

for (const [orderType, orders] of Object.entries(orderData)) {
  postpaidUserIds.push(orders.filter((order) => order.type === 'postpaid'));
}
console.log(postpaidUserIds);

Gissa hur postpaidUserIds ser ut nu? Ledtråd: det är inte vackert!

[
  [],
  [
    { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' },
    { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' }
  ]
]

Om du är en rationell person vill du inte skriva egen kod för att extrahera beställningsobjekten och lägga dem i en enda array. Använd bara flatten()-metoden och njut av resultatet:

const flatUserIds = _.flatten(postpaidUserIds);
console.log(flatUserIds);
/*
[
  { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' },
  { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' }
]
*/

Observera att flatten() bara går en nivå djupt. Det vill säga om dina objekt är kapslade två, tre eller fler nivåer djupt kommer flatten() inte att vara tillräckligt. I sådana fall har Lodash metoden flattenDeep(), men var medveten om att användningen av den här metoden på mycket stora strukturer kan göra det långsamt (eftersom det i bakgrunden finns en rekursiv operation).

Är ett objekt/en array tom?

Tack vare hur ”falska” värden och typer fungerar i JavaScript, kan något så enkelt som att leta efter tomhet ibland skapa existentiell ångest.

Hur kollar man om en array är tom? Du kan kontrollera om dess längd är 0 eller inte. Och hur kollar man om ett objekt är tomt? Jo… vänta lite! Det är nu den obehagliga känslan börjar komma, och JavaScript-exempel som visar att [] == false och {} == false börjar snurra i huvudet. När du har press på dig att leverera en funktion är sådana här fallgropar det sista du behöver. De gör koden svårare att förstå och skapar osäkerhet i dina tester.

Hantera saknad data

I den verkliga världen är data sällan perfekt och strömlinjeformad. Ett vanligt exempel är att objekt/arrayer kan saknas i en stor datastruktur som tas emot via ett API-svar.

Anta att vi fått följande objekt som ett API-svar:

const apiResponse = {
  id: 33467,
  paymentRefernce: 'AEE3356T68',
  // `order` object missing
  processedAt: `2021-10-10 00:00:00`,
};

Som vi ser får vi i allmänhet ett order-objekt i svaret från API:et, men det är inte alltid fallet. Vad händer då om vi har kod som förlitar sig på det här objektet? Ett sätt är att skriva defensiv kod, men beroende på hur djupt order-objektet är kapslat, så skulle vi snart skriva väldigt ful kod om vi vill undvika fel vid körning:

if (
  apiResponse.order &&
  apiResponse.order.payee &&
  apiResponse.order.payee.address
) {
  console.log(
    'The order was sent to the zip code: ' +
      apiResponse.order.payee.address.zipCode
  );
}

🤢🤢 Ja, väldigt fult att skriva, väldigt fult att läsa, väldigt fult att underhålla och så vidare. Lyckligtvis har Lodash ett enkelt sätt att hantera sådana situationer.

const zipCode = _.get(apiResponse, 'order.payee.address.zipCode');
console.log('The order was sent to the zip code: ' + zipCode);
// The order was sent to the zip code: undefined

Det finns också det fantastiska alternativet att ange ett standardvärde istället för att få värdet ”undefined” för saknade saker:

const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA');
console.log('The order was sent to the zip code: ' + zipCode2);
// The order was sent to the zip code: NA

Jag vet inte hur det är med dig, men get() är en av de saker som gör att jag får tårar av glädje. Det är inget extravagant. Det finns ingen ovanlig syntax eller alternativ att memorera, men tänk hur mycket kollektivt lidande det kan lindra! 😇

Debouncing

Om du inte känner till begreppet ”debouncing” så är det ett vanligt tema inom frontend-utveckling. Idén är att det ibland är fördelaktigt att inte starta en åtgärd direkt, utan efter en viss tid (vanligtvis några millisekunder). Vad betyder det? Här är ett exempel.

Tänk dig en e-handelssajt med ett sökfält (egentligen vilken sajt/app som helst!). För att ge en bättre användarupplevelse vill vi inte att användaren ska behöva trycka på enter (eller ännu värre, på ”sök”-knappen) för att se förslag baserade på deras sökterm. Men den uppenbara lösningen är inte optimal: om vi lägger till en händelselyssnare till onChange() för sökfältet och skickar ett API-anrop för varje tangenttryckning, skulle vi skapa en mardröm för vår backend. Det skulle bli för många onödiga anrop (om t.ex. ”vit matta borste” söks kommer det totalt 18 förfrågningar!) och nästan alla dessa kommer att vara irrelevanta eftersom användarens inmatning inte är slutförd.

Lösningen ligger i debouncing, och tanken är denna: skicka inte ett API-anrop så fort texten ändras. Vänta en stund (säg 200 millisekunder), och om det under den tiden sker en ny tangenttryckning, avbryt den tidigare tidsräkningen och börja vänta igen. Resultatet blir att det är först när användaren tar en paus (antingen för att hen funderar eller för att hen är klar och förväntar sig ett svar) som vi skickar en API-förfrågan till backend.

Den övergripande strategin som jag beskrivit är komplicerad, och jag kommer inte gå in på synkroniseringen av tidshantering och avbokning. Men själva debouncing-processen är väldigt enkel om du använder Lodash.

const _ = require('lodash');
const axios = require('axios');

// Det här är ett riktigt hund-API!
const fetchDogBreeds = () =>
  axios
    .get('https://dog.ceo/api/breeds/list/all')
    .then((res) => console.log(res.data));

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // efter en sekund
debouncedFetchDogBreeds(); // visar data efter en stund

Om du tänker att setTimeout() hade gjort samma sak, så stämmer det inte riktigt. Lodashs debounce har många kraftfulla funktioner. Du kanske vill se till att debouncingen inte blir oändlig. Även om det alltså sker en tangenttryckning varje gång funktionen ska starta (och därmed avbryter processen), kanske du vill se till att API-anropet skickas i alla fall, efter till exempel två sekunder. För det har Lodash debounce()-alternativet maxWait:

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // Debounce i 250 ms, men skicka API-anropet efter 2 sekunder

Kolla in dokumentationen för en mer djupgående genomgång. Den är full av mycket viktig information!

Ta bort värden från en array

Jag vet inte hur det är med dig, men jag hatar att skriva kod för att ta bort objekt från en array. Först måste jag hämta indexet för objektet, kontrollera om indexet verkligen är giltigt och i så fall anropa metoden splice() och så vidare. Jag kommer aldrig ihåg syntaxen och måste därför leta upp den hela tiden, och när det är klart känner jag mig orolig för att jag har låtit en bugg smita in.

const greetings = ['hello', 'hi', 'hey', 'wave', 'hi'];
_.pull(greetings, 'wave', 'hi');
console.log(greetings);
// [ 'hello', 'hey' ]

Observera två saker:

  • Den ursprungliga arrayen ändrades under processen.
  • Metoden pull() tar bort alla instanser, även om det finns dubbletter.

Det finns en annan relaterad metod som heter pullAll() som tar en array som andra parameter, vilket gör det enklare att ta bort flera objekt samtidigt. Visst, vi kan använda pull() med spread-operatorn, men kom ihåg att Lodash skapades vid en tidpunkt då spread-operatorn inte ens var på tal i språket!

const greetings2 = ['hello', 'hi', 'hey', 'wave', 'hi'];
_.pullAll(greetings2, ['wave', 'hi']);
console.log(greetings2);
// [ 'hello', 'hey' ]

Sista index för ett element

JavaScripts ursprungliga indexOf()-metod är bra, förutom när man vill söka igenom arrayen från motsatt håll! Och visst, man kan bara skriva en minskande loop och hitta elementet, men varför inte använda en mycket elegantare metod?

Här är en snabb Lodash-lösning med metoden lastIndexOf():

const integers = [2, 4, 1, 6, -1, 10, 3, -1, 7];
const index = _.lastIndexOf(integers, -1);
console.log(index); // 7

Tyvärr finns det ingen variant av den här metoden där vi kan söka efter komplexa objekt eller skicka en egen sökfunktion.

Zippa och packa upp!

Om du inte har arbetat med Python kanske du aldrig märker eller föreställer dig zip/unzip under hela din karriär som JavaScript-utvecklare. Kanske av goda skäl, det finns sällan ett så desperat behov av zip/unzip som det finns för filter() etc. Det är dock ett av de mindre kända verktygen som finns och kan hjälpa dig att skapa kortfattad kod i vissa situationer.

I motsats till vad det låter som har zip/unzip inget med komprimering att göra. Istället är det en grupperingsoperation där arrayer av samma längd kan konverteras till en enda array av arrayer med element på samma position packade tillsammans (zip()) och tillbaka (unzip()). Jag vet, det börjar bli rörigt när man försöker förklara med ord, så låt oss titta på lite kod:

const animals = ['duck', 'sheep'];
const sizes = ['small', 'large'];
const weight = ['less', 'more'];

const groupedAnimals = _.zip(animals, sizes, weight);
console.log(groupedAnimals);
// [ [ 'duck', 'small', 'less' ], [ 'sheep', 'large', 'more' ] ]

De ursprungliga tre arrayerna konverterades till en enda array med endast två arrayer. Och var och en av de nya arrayerna representerar ett djur med all information samlad på samma ställe. Index 0 anger vilken typ av djur det är, index 1 anger dess storlek och index 2 dess vikt. Resultatet är att data är enklare att hantera. När du har utfört de operationer du behöver på data, kan du dela upp det igen med unzip() och återställa det till den ursprungliga formen:

const animalData = _.unzip(groupedAnimals);
console.log(animalData);
// [ [ 'duck', 'sheep' ], [ 'small', 'large' ], [ 'less', 'more' ] ]

Zip/unzip är inget som kommer att förändra ditt liv på en gång, men det kommer att förändra ditt liv en dag!

Slutsats 👨‍🏫

(Jag lägger all källkod som använts i den här artikeln här så att du kan prova direkt i webbläsaren!)

Lodashs dokumentation är full av exempel och funktioner som kan verka förvirrande. I en tid där masochismen verkar öka i JS-miljön, är Lodash en frisk fläkt, och jag rekommenderar starkt att du använder det här biblioteket i dina projekt!