Lodash for JS-utviklere: 10 essensielle funksjoner

Et dykk inn i Lodash: Et Undervurdert JavaScript-Bibliotek

For JavaScript-utviklere er Lodash et velkjent navn. Likevel kan biblioteket virke stort og overveldende. Men det trenger det ikke lenger å være!

Lodash, Lodash, Lodash… Hvor skal man begynne? 🤔

I en tid hvor JavaScript-økosystemet var i sin spede begynnelse, kan det sammenlignes med det ville vesten eller en jungel – mye foregikk, men det var få løsninger på vanlige utviklerfrustrasjoner og behov for produktivitet.

Så kom Lodash inn på scenen, som en flodbølge som feide over alt. Fra enkle hverdagsoppgaver som sortering, til komplekse datatransformasjoner, var Lodash stappfullt (kanskje litt for mye!) med funksjonalitet som gjorde JavaScript-utviklernes liv mye enklere.

Hei, Lodash!

Hvor står Lodash i dag? Jo, det har fremdeles alle de samme fordelene som før, og kanskje enda litt til, men det ser ut til å ha mistet popularitet i JavaScript-fellesskapet. Hvorfor? Jeg kan se for meg noen grunner:

  • Noen funksjoner i Lodash var (og er fortsatt) trege når de brukes på store lister. Selv om dette ikke ville ha påvirket 95% av prosjektene, gav innflytelsesrike utviklere fra de resterende 5% Lodash dårlig omtale, og denne effekten spredte seg.
  • Det er en tendens i JS-økosystemet (og delvis i Golang-miljøet) hvor arroganse er mer vanlig enn nødvendig. Å stole på noe som Lodash blir sett på som «dumt» og blir avfeid på fora som StackOverflow når folk foreslår slike løsninger («Hæ?! Bruke et helt bibliotek til noe sånt? Jeg kan kombinere filter() med reduce() for å oppnå det samme i en enkel funksjon!»).
  • Lodash er gammelt. I hvert fall etter JS-standarder. Det ble lansert i 2012, noe som betyr at det er nesten ti år gammelt i skrivende stund. API-et har vært stabilt, og det er ikke mange spennende nyheter som kan legges til hvert år (rett og slett fordi det ikke er nødvendig), noe som skaper en følelse av kjedsomhet hos den gjennomsnittlige, ivrige JS-utvikleren.

Etter min mening er det å ikke bruke Lodash et betydelig tap for JavaScript-kodebasene våre. Det har bevist pålitelige og elegante løsninger for vanlige problemer vi møter på jobben, og bruken vil kun gjøre koden vår mer lesbar og vedlikeholdbar.

Med dette i bakhodet, la oss dykke ned i noen av de mest vanlige (og ikke fullt så vanlige!) Lodash-funksjonene og se hvor utrolig nyttig og kraftfullt dette biblioteket er.

Kloning – Dypt!

Siden objekter sendes ved referanse i JavaScript, skaper det en hodepine for utviklere når de ønsker å kopiere noe og forvente at det nye datasettet er uavhengig av originalen.

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

      // Finn folk som skriver i C++
      let folksDoingCpp = people.filter((person) => person.specialization == 'C++');

      // Konverter dem til 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' }
      ]
      */
    

Legg merke til hvordan den originale gruppen av mennesker ble endret i prosessen (Arnolds spesialitet endret seg fra C++ til JS), til tross for våre gode intensjoner! Dette er et stort slag for integriteten til det underliggende systemet. Vi trenger en måte å lage en ekte (dyp) kopi av den opprinnelige matrisen.

Man kan argumentere for at dette er en «tullete» måte å kode på i JS, men virkeligheten er mer komplisert. Ja, vi har den fantastiske destruktureringoperatoren, men alle som har prøvd å destrukturere komplekse objekter og matriser, vet at det kan være en smertefull opplevelse. Så har vi ideen om å bruke serialisering og deserialisering (kanskje med JSON) for å oppnå dyp kopiering, men det gjør koden vanskeligere å lese.

Se hvor elegant og konsis løsningen er med Lodash:

      const _ = require('lodash');

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

      let peopleCopy = _.cloneDeep(people);

      // Finn folk som skriver i C++
      let folksDoingCpp = peopleCopy.filter(
        (person) => person.specialization == 'C++'
      );

      // Konverter dem til 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' }
      ]
      */
    

Legg merke til hvordan den originale `people`-matrisen forblir uendret etter dyp kloning. Arnold er fortsatt en C++-spesialist. Og like viktig, koden er enkel å forstå.

Fjern Duplikater fra En Matrise

Å fjerne duplikater fra en matrise høres ut som en utmerket oppgave for et jobbintervju (husk, hvis du er usikker, bruk et hashmap!). Selvsagt kan du skrive en egen funksjon, men hva om du må gjøre matrisen unik i flere scenarioer? Du kan skrive flere funksjoner (og risikere å støte på subtile feil), eller du kan bare bruke Lodash!

Vårt første eksempel på unike matriser er enkelt, men det illustrerer hastigheten og påliteligheten som Lodash bringer til bordet. Tenk deg å skrive all denne logikken selv!

      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 ]
    

Merk at den endelige matrisen ikke er sortert, noe som ikke er viktig her. Men la oss se for oss et mer komplisert scenario: vi har en liste over brukere hentet fra et sted, og vi vil sørge for at listen kun inneholder unike brukere. 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 dette eksemplet brukte vi `uniqBy()`-metoden for å fortelle Lodash at vi vil at objektene skal være unike basert på id-egenskapen. På én linje uttrykte vi noe som kunne ha tatt 10-20 linjer og som kunne ha introdusert flere feil!

Det finnes flere metoder for å fjerne duplikater i Lodash. Jeg oppfordrer deg til å ta en titt på dokumentasjonen.

Forskjellen Mellom To Matriser

Union, forskjell, osv. kan høres ut som begreper fra kjedelige matteforelesninger, men de dukker opp ganske ofte i daglig bruk. Det er vanlig å ha en liste og ville slå den sammen med en annen, eller å finne elementene som er unike for en liste sammenlignet med en annen. For disse scenarioene er `difference`-funksjonen perfekt.

La oss starte med et enkelt scenario: Du har en liste over alle bruker-ID-er i systemet og en liste over dem som har aktive kontoer. Hvordan finner du de inaktive ID-ene? Enkelt, ikke sant?

      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 ]
    

Hva om du, som i en mer realistisk setting, må jobbe med objekter i stedet for primitive verdier? Vel, Lodash har en fin `differenceBy()`-metode for dette!

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

Ganske enkelt, eller hva?!

I likhet med `difference`, finnes det andre metoder i Lodash for vanlige settoperasjoner: `union`, `intersection`, osv.

Flat ut Matriser

Behovet for å flate ut matriser oppstår ganske ofte. Et eksempel er at du har mottatt et API-svar og må bruke en kombinasjon av `map()` og `filter()` på en kompleks liste over nestede objekter/matriser for å plukke ut, for eksempel bruker-ID-er, og nå sitter du igjen med matriser av matriser. Her er et kodeeksempel som viser dette:

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

      // Finn bruker-ID-er som har lagt inn postpaid-bestillinger (interne eller eksterne)
      const postpaidUserIds = [];

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

Kan du gjette hvordan `postpaidUserIds` ser ut nå? Hint: det er ikke pent!

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

Hvis du er fornuftig, vil du ikke skrive en tilpasset logikk for å trekke ut bestillingsobjektene og legge dem på en enkelt matrise. Bare bruk `flatten()`-metoden og nyt fruktene:

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

Legg merke til at `flatten()` kun går ett nivå ned. Hvis objektene dine er nestet to, tre eller flere nivåer dypt, vil `flatten()` skuffe deg. I disse tilfellene har Lodash en `flattenDeep()`-metode, men vær oppmerksom på at bruk av denne metoden på store strukturer kan bremse ting (siden den utfører en rekursiv operasjon).

Er Objektet/Matrisen Tom?

Takket være hvordan «falske» verdier og typer fungerer i JavaScript, kan selv en så enkel oppgave som å sjekke om noe er tomt, føles som en eksistensiell krise.

Hvordan sjekker du om en matrise er tom? Du kan sjekke om lengden er 0 eller ikke. Hvordan sjekker du om et objekt er tomt? Vel… vent litt! Det er her en urolig følelse begynner å snike seg inn, og tankene på at `[] == false` og `{}` == `false` begynner å kverne. Når du har et press for å levere en funksjon, er disse fallgruvene det siste du trenger – de vil gjøre koden din vanskelig å forstå og skape usikkerhet i testene dine.

Arbeid med Manglende Data

I den virkelige verden er data sjelden perfekte. Uansett hvor mye vi ønsker det, er det ikke alltid strømlinjeformet og fornuftig. Et typisk eksempel er null-objekter/matriser som mangler i en stor datastruktur mottatt som API-svar.

Anta at vi mottok følgende objekt som et API-svar:

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

Som vist får vi vanligvis et `order`-objekt i svaret fra API, men det er ikke alltid tilfelle. Hva om vi har kode som er avhengig av dette objektet? En måte er å kode defensivt, men avhengig av hvor dypt `order`-objektet er nestet, vil vi raskt ende opp med veldig stygg kode hvis vi skal unngå feil:

      if (
        apiResponse.order &&
        apiResponse.order.payee &&
        apiResponse.order.payee.address
      ) {
        console.log(
          'Bestillingen ble sendt til postnummer: ' +
            apiResponse.order.payee.address.zipCode
        );
      }
    

🤢🤢 Ja, det er stygt å skrive, stygt å lese, stygt å vedlikeholde osv. Heldigvis har Lodash en ryddig måte å håndtere slike situasjoner på.

      const zipCode = _.get(apiResponse, 'order.payee.address.zipCode');
      console.log('Bestillingen ble sendt til postnummer: ' + zipCode);
      // Bestillingen ble sendt til postnummer: undefined
    

Det finnes også et fantastisk alternativ for å gi en standardverdi i stedet for undefined hvis noe mangler:

      const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA');
      console.log('Bestillingen ble sendt til postnummer: ' + zipCode2);
      // Bestillingen ble sendt til postnummer: NA
    

Jeg vet ikke med deg, men `get()` er en av de tingene som får meg til å gråte av glede. Det er ikke prangende. Det er ingen komplisert syntaks eller alternativer å huske, men se hvor mye kollektiv lidelse det kan lindre! 😇

Debouncing

Hvis du ikke er kjent med begrepet, er debouncing et vanlig tema innen frontend-utvikling. Tanken er at det noen ganger er fordelaktig å starte en handling ikke umiddelbart, men etter en viss tid (vanligvis noen få millisekunder). Hva betyr det? Her er et eksempel.

Tenk deg en nettbutikk med et søkefelt. For en bedre brukeropplevelse ønsker vi ikke at brukeren skal trykke enter (eller enda verre, trykke på «søk»-knappen) for å se forslag/forhåndsvisninger basert på søkeordet. Men den åpenbare løsningen er litt problematisk: Hvis vi legger til en event listener til `onChange()` for søkefeltet og skyter et API-kall for hvert tastetrykk, ville det vært et mareritt for backend. Det ville være for mange unødvendige kall (hvis for eksempel «hvit teppebørste» søkes, vil det være totalt 18 forespørsler!), og nesten alle disse vil være irrelevante fordi brukerens input ikke er fullført.

Løsningen ligger i debouncing. Ideen er: Ikke send et API-kall med en gang teksten endres. Vent en stund (f.eks. 200 millisekunder), og hvis det er et nytt tastetrykk innen den tiden, avbryt den forrige tidtakingen og begynn å vente på nytt. Det betyr at kun når brukeren tar en pause (enten fordi de tenker eller fordi de er ferdige og forventer et svar), sender vi en API-forespørsel til backend.

Strategien jeg har beskrevet, er komplisert, og jeg vil ikke dykke ned i synkronisering av tidsstyring og kansellering, men debouncing i seg selv er veldig enkelt hvis du bruker Lodash.

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

      // Dette er forresten en ekte hunderase-API!
      const fetchDogBreeds = () =>
        axios
          .get('https://dog.ceo/api/breeds/list/all')
          .then((res) => console.log(res.data));

      const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // etter ett sekund
      debouncedFetchDogBreeds(); // viser data etter en stund
    

Hvis du tenker at `setTimeout()` kunne ha gjort den samme jobben, så er det mer til det! Lodash sin `debounce()` har mange kraftfulle funksjoner. Du kan for eksempel sørge for at avvisningen ikke er uendelig. Det vil si at selv om det er et nytt tastetrykk hver gang funksjonen er i ferd med å utløses (og dermed avbryter den totale prosessen), kan det være lurt å sikre at API-kallet sendes uansett etter for eksempel to sekunder. For dette har Lodash `debounce()`-alternativet `maxWait`:

      const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // Debounce i 250ms, men send API-forespørselen etter 2 sekunder uansett
    

Sjekk ut den offisielle dokumentasjonen for en dypere forståelse. Det er massevis av nyttig informasjon der!

Fjern Verdier Fra En Matrise

Jeg vet ikke med deg, men jeg hater å skrive kode for å fjerne elementer fra en matrise. Først må jeg hente elementets indeks, sjekke om indeksen er gyldig og deretter kalle `splice()`-metoden. Jeg husker aldri syntaksen og må slå opp ting hele tiden, og til slutt sitter jeg igjen med en følelse av at jeg har introdusert en eller annen dum feil.

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

Vær oppmerksom på to ting:

  • Den opprinnelige matrisen ble endret i prosessen.
  • `pull()`-metoden fjerner alle forekomster, selv om det finnes duplikater.

Det finnes også en annen relatert metode kalt `pullAll()` som aksepterer en matrise som den andre parameteren, noe som gjør det enklere å fjerne flere elementer samtidig. Vi kunne også brukt `pull()` med en spread-operator, men husk at Lodash ble utviklet på en tid hvor spread-operatoren ikke engang var et forslag i språket!

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

Siste Indeks For Et Element

JavaScript sin innebygde `indexOf()`-metode er grei, bortsett fra når du er interessert i å skanne matrisen fra motsatt retning! Og igjen, ja, du kan skrive en dekrementerende løkke og finne elementet, men hvorfor ikke bruke en mer elegant teknikk?

Her er en rask Lodash-løsning som bruker `lastIndexOf()`-metoden:

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

Dessverre finnes det ingen variant av denne metoden hvor vi kan slå opp komplekse objekter eller sende inn en egendefinert oppslagsfunksjon.

Zip. Pakk ut!

Med mindre du har jobbet i Python, er zip/unzip et verktøy du kanskje aldri legger merke til eller forestiller deg i din karriere som JavaScript-utvikler. Kanskje det er en god grunn til det: Det er sjeldent et desperat behov for zip/unzip på samme måte som for `filter()` osv. Men det er et av de beste mindre kjente verktøyene og kan hjelpe deg å skrive konsis kode i visse situasjoner.

I motsetning til hva navnet kan tilsi, har zip/unzip ingenting med komprimering å gjøre. Det er en grupperingsoperasjon hvor matriser av samme lengde kan konverteres til en enkelt matrise av matriser, hvor elementer i samme posisjon pakkes sammen (`zip()`) og tilbake igjen (`unzip()`). Jeg vet det er vanskelig å forstå med ord, så la oss se på et eksempel:

      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 originale tre matrisene ble konvertert til en enkelt matrise med kun to matriser. Hver av disse nye matrisene representerer et dyr med all informasjon på ett sted. Indeks 0 forteller oss dyretypen, indeks 1 størrelsen og indeks 2 vekten. Dataene er nå enklere å jobbe med. Når du har utført de operasjonene du trenger på dataene, kan du splitte dem opp igjen ved å bruke `unzip()` og sende dem tilbake til kilden:

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

Zip/unzip er kanskje ikke noe som forandrer livet ditt over natten, men det kan forandre det en vakker dag!

Konklusjon 👨‍🏫

(Jeg har lagt inn all kildekoden fra denne artikkelen her for deg å prøve direkte i nettleseren!)

Lodash dokumentasjonen er fylt med eksempler og funksjoner som lett kan forvirre deg. I en tid hvor masochisme ser ut til å være i vekst i JS-økosystemet, er Lodash et friskt pust, og jeg anbefaler at du bruker dette biblioteket i dine prosjekter!