10 viktiga Lodash-funktioner för JavaScript-utvecklare

För JavaScript-utvecklare behöver Lodash ingen introduktion. Biblioteket är dock stort och känns ofta överväldigande. Inte längre!

Lodash, Lodash, Lodash. . . var ska jag ens börja! 🤔

Det fanns en tid när JavaScript-ekosystemet var begynnande; det skulle kunna jämföras med vilda västern eller en djungel om man så vill, där mycket pågick, men det fanns väldigt få svar på vardagliga utvecklarfrustrationer och produktivitet.

Sedan Lodash kom in på scenen, och det kändes som en översvämning som sänkte allt. Från enkla vardagliga behov som sortering till komplexa datastrukturtransformationer kom Lodash laddad (överbelastad, till och med!) med funktionalitet som förvandlade JS-utvecklarnas liv till ren lycka.

Hej Lodash!

Och var är Lodash idag? Tja, den har fortfarande alla godsaker som den erbjöd från början, och lite till, men den verkar ha tappat förståndet i JavaScript-gemenskapen. Varför? Jag kan komma på några anledningar:

  • Vissa funktioner i Lodash-biblioteket var (och är fortfarande) långsamma när de tillämpades på stora listor. Även om detta aldrig skulle ha påverkat 95 % av projekten där ute, gav inflytelserika utvecklare från de återstående 5 % Lodash en dålig press och effekten föll ner i gräsrötterna.
  • Det finns en trend i JS-ekosystemet (kan till och med säga samma sak om Golang-folket) där hybris är vanligare än nödvändigt. Så att förlita sig på något som Lodash ses som dumt och blir nedskjutet på forum som StackOverflow när folk föreslår sådana lösningar (”Vad?! Använd ett helt bibliotek för något sånt här? Jag kan kombinera filter() med reduce() för att uppnå samma sak i en enkel funktion!”).
  • Lodash är gammal. Åtminstone enligt JS-standard. Den kom ut 2012, så i skrivande stund har det gått nästan tio år. API:t har varit stabilt och inte mycket spännande saker kan läggas till varje år (helt enkelt för att det inte behövs), vilket skapar tristess för den genomsnittliga överexiterade JS-utvecklaren.

Enligt min åsikt är att inte använda Lodash en betydande förlust för våra JavaScript-kodbaser. Den har bevisat felfria och eleganta lösningar för vardagliga problem vi stöter på på jobbet, och att använda den kommer bara att göra vår kod mer läsbar och underhållbar.

Med det sagt, låt oss dyka in i några av de vanliga (eller inte!) Lodash-funktionerna och se hur otroligt användbart och vackert det här biblioteket är.

Klona. . . djupt!

Eftersom objekt skickas med referens i JavaScript skapar det en huvudvärk för utvecklare när de vill klona något med hopp om att den nya datamängden är annorlunda.

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

// Find people writing in C++
let folksDoingCpp = people.filter((person) => person.specialization == 'C++');

// Convert them to 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' }
]
*/

Lägg märke till hur i vår rena oskuld och trots våra goda avsikter, muterade den ursprungliga folkgruppen i processen (Arnolds specialisering ändrades från C++ till JS) — ett stort slag mot integriteten hos det underliggande mjukvarusystemet! Faktum är att vi behöver ett sätt att göra en sann (djup) kopia av den ursprungliga arrayen.

Hej Dave, träffa Dave!

Du kan kanske hävda att det här är ett ”fånigt” sätt att koda i JS; verkligheten är dock lite komplicerad. Ja, vi har den underbara destruktureringsoperatören tillgänglig, men alla som har försökt destrukturera komplexa objekt och arrayer känner till smärtan. Sedan finns det idén att använda serialisering och avserialisering (kanske JSON) för att uppnå djupkopiering, men det gör bara din kod rörigare för läsaren.

Se däremot hur otroligt elegant och koncis lösningen är när Lodash blir van:

const _ = require('lodash');

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

let peopleCopy = _.cloneDeep(people);

// Find people writing in C++
let folksDoingCpp = peopleCopy.filter(
  (person) => person.specialization == 'C++'
);

// Convert them to 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' }
]
*/

Lägg märke till hur folkgruppen är orörd efter djupkloning (Arnold är fortfarande specialiserad på C++ i det här fallet). Men ännu viktigare, koden är enkel att förstå.

Ta bort dubbletter från en array

Att ta bort dubbletter från en array låter som ett utmärkt intervju/whiteboard-problem (kom ihåg, när du är osäker, kasta en hashmap på problemet!). Och naturligtvis kan du alltid skriva en anpassad funktion för att göra det, men vad händer om du stöter på flera olika scenarier för att göra dina arrayer unika? Du kan skriva flera andra funktioner för det (och riskera att stöta på subtila buggar), eller så kan du bara använda Lodash!

Vårt första exempel på unika arrayer är ganska trivialt, men det representerar fortfarande hastigheten och tillförlitligheten som Lodash ger till bordet. Föreställ dig att du gör detta genom att skriva all anpassad 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 ]

Lägg märke till att den slutliga arrayen inte är sorterad, vilket naturligtvis inte är av någon anledning här. Men nu, låt oss föreställa oss ett mer komplicerat scenario: vi har en mängd användare som vi hämtade från någonstans, men vi vill se till att det bara innehåller unika användare. 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 tala om för Lodash att vi vill att objekten ska vara unika på id-egenskapen. På en rad uttryckte vi vad som kunde ha tagit 10-20 rader och introducerat mer utrymme för buggar!

Det finns mycket mer tillgängligt för att göra saker unika i Lodash, och jag uppmuntrar dig att ta en titt på docs.

Skillnaden mellan två arrayer

Union, skillnad, etc., kan låta som termer som man bäst lämnar bakom sig i tråkiga gymnasieföreläsningar om uppsättningsteori, men de dyker upp oftare än inte i den dagliga praktiken. Det är vanligt att man har en lista och vill slå ihop en annan lista med den eller att man vill hitta vilka element som är unika för den jämfört med en annan lista; för dessa scenarier är skillnadsfunktionen perfekt.

Hej, A. Hejdå, B!

Låt oss börja resan till skillnad genom att ta ett enkelt scenario: du har fått 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 det händer i en mer realistisk miljö, måste arbeta med en rad objekt istället för vanliga primitiver? Tja, Lodash har en bra differenceBy()-metod för detta!

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' } ]

Snyggt va?!

Liksom skillnaden finns det andra metoder i Lodash för vanliga setoperationer: union, korsning, etc.

Platta ut arrayer

Behovet av att platta arrayer uppstår ganska ofta. Ett användningsfall ä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 plocka ut, till exempel, användar-ID, och nu har du kvar arrayer av arrayer. Här är ett kodavsnitt som skildrar denna situation:

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' },
  ],
};

// find user ids that placed postpaid orders (internal or external)
const postpaidUserIds = [];

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

Kan du gissa hur postPaidUserIds ser ut nu? Tips: det är äckligt!

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

Nu, om du är en förnuftig person, vill du inte skriva anpassad logik för att extrahera ordningsobjekten och lägga ut dem snyggt i rad i en array. Använd bara metoden flatten() och njut av druvorna:

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 har fastnat två, tre eller fler nivåer djupt, flatten() kommer de att göra dig besviken. I dessa fall har Lodash metoden flattenDeep(), men varnas för att tillämpningen av den här metoden på mycket stora strukturer kan sakta ner saker och ting (som bakom kulisserna finns det en rekursiv operation på jobbet).

Är objektet/matrisen tom?

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

Hur kontrollerar man om en array är tom? Du kan kontrollera om dess längd är 0 eller inte. Nu, hur kontrollerar du om ett objekt är tomt? Tja…vänta lite! Det är här den obehagliga känslan infinner sig, och de där JavaScript-exemplen som innehåller saker som [] == falskt och {} == falskt börjar cirkla runt våra huvuden. När du är under press för att leverera en funktion är landminor som dessa det sista du behöver – de kommer att göra din kod svår att förstå, och de kommer att introducera osäkerhet i din testsvit.

Arbetar med saknad data

I den verkliga världen lyssnar data på oss; hur gärna vi än vill ha det så är det sällan strömlinjeformat och sunt. Ett typiskt exempel är att nullobjekt/matriser saknas i en stor datastruktur som tas emot som API-svar.

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

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

Som visas får vi i allmänhet ett orderobjekt i svaret från API:t, men det är inte alltid fallet. Så, vad händer om vi har någon kod som förlitar sig på det här objektet? Ett sätt skulle vara att koda defensivt, men beroende på hur kapslat orderobjektet är, skulle vi snart skriva väldigt ful kod om vi vill undvika runtime-fel:

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. Tack och lov 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 tillhandahålla ett standardvärde istället för att bli odefinierat 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 får mina ögon att få glädjetårar. Det är inget flashigt. Det finns ingen konsulterad syntax eller alternativ att memorera, men titta på hur mycket kollektivt lidande det kan lindra! 😇

Avstudsande

Om du inte är bekant är debouncing ett vanligt tema i frontend-utveckling. Tanken är att det ibland är fördelaktigt att starta en åtgärd inte omedelbart utan efter en tid (vanligtvis några millisekunder). Vad betyder det? Här är ett exempel.

Föreställ dig en e-handelswebbplats med ett sökfält (tja, vilken webbplats/webbapp som helst nuförtiden!). För bättre UX vill vi inte att användaren ska behöva trycka på enter (eller ännu värre, tryck på ”sök”-knappen) för att visa förslag/förhandsvisningar baserat på deras sökterm. Men det uppenbara svaret är lite laddat: om vi lägger till en händelseavlyssnare till onChange() för sökfältet och avfyrar ett API-anrop för varje tangenttryckning, skulle vi ha skapat en mardröm för vår backend; det skulle bli för många onödiga samtal (om t.ex. ”white carpet brush” söks kommer det att finnas totalt 18 förfrågningar!) och nästan alla dessa kommer att vara irrelevanta eftersom användarinmatningen inte har slutförts.

Svaret ligger i debouncing, och tanken är denna: skicka inte ett API-anrop så fort texten ändras. Vänta ett tag (säg 200 millisekunder) och om det vid den tiden finns en ny tangenttryckning, avbryt den tidigare tidsräkningen och börja vänta igen. Som ett resultat är det bara när användaren pausar (antingen för att de tänker eller för att de är klara och de förväntar sig något svar) som vi skickar en API-förfrågan till backend.

Den övergripande strategin jag beskrev är komplicerad, och jag kommer inte att dyka in i synkroniseringen av timerhantering och avbokning; Men den faktiska avstudsningsprocessen är väldigt enkel om du använder Lodash.

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

// This is a real dogs' API, by the way!
const fetchDogBreeds = () =>
  axios
    .get('https://dog.ceo/api/breeds/list/all')
    .then((res) => console.log(res.data));

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // after one second
debouncedFetchDogBreeds(); // shows data after some time

Om du tänker setTimeout() skulle jag ha gjort samma jobb, ja, det finns mer! Lodashs debounce kommer med många kraftfulla funktioner; Du kanske till exempel vill se till att avvisningen inte är obestämd. Det vill säga, även om det är en tangenttryckning varje gång funktionen är på väg att starta (och därmed avbryter den övergripande processen), kanske du vill se till att API-anropet görs ändå efter, säg, två sekunder. För detta har Lodash debounce() alternativet maxWait:

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // debounce for 250ms, but send the API request after 2 seconds anyway

Kolla in tjänstemannen docs för ett djupare dyk. De är fulla av superviktiga saker!

Ta bort värden från en array

Jag vet inte om dig, men jag hatar att skriva kod för att ta bort objekt från en array. Först måste jag hämta objektets index, kontrollera om indexet verkligen är giltigt och i så fall anropa metoden splice() och så vidare. Jag kommer aldrig ihåg syntaxen och behöver därför leta upp saker hela tiden, och i slutet av det sitter jag kvar med den tjatande känslan av att jag har låtit någon dum bugg smyga sig 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 accepterar en array som den andra parametern, vilket gör det lättare att ta bort flera objekt samtidigt. Visst att vi bara kunde använda pull() med en spread-operator, men kom ihåg att Lodash kom vid en tidpunkt då spread-operatorn inte ens var ett förslag på språket!

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

    Sista indexet för ett element

    JavsScripts ursprungliga indexOf()-metod är cool, förutom när du är intresserad av att skanna arrayen från motsatt riktning! Och återigen, ja, du kan bara skriva en dekrementerande slinga och hitta elementet, men varför inte använda en mycket mer elegant teknik?

    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 denna metod där vi kan slå upp komplexa objekt eller ens skicka en anpassad uppslagsfunktion.

    Blixtlås. Packa upp!

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

    Tvärtemot 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()). Ja, jag vet, det börjar bli disigt att försöka nöja sig 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 med endast två arrayer. Och var och en av dessa nya arrayer representerar ett enda djur med allt på ett ställe. Så, index 0 talar om för oss vilken typ av djur det är, index 1 talar om för oss dess storlek, och index 2 berättar för oss dess vikt. Som ett resultat är data nu lättare att arbeta med. När du har tillämpat alla operationer du behöver på data kan du bryta upp den igen med unzip() och skicka tillbaka den till den ursprungliga källan:

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

    Zip/unzip-verktyget är inte något som kommer att förändra ditt liv över en natt, men det kommer att förändra ditt liv en dag!

    Slutsats 👨‍🏫

    (Jag lägger all källkod som används i den här artikeln här för dig att prova direkt från webbläsaren!)

    Lodashen docs är proppfulla av exempel och funktioner som bara kommer att förvirra dig. I en tid där masochism verkar öka i JS-ekosystemet, är Lodash som en frisk fläkt, och jag rekommenderar starkt att du använder det här biblioteket i dina projekt!