Sådan krydser du et objekt rekursivt med JavaScript

Hvordan man skriver en funktion, der leder efter et bestemt nøgle/værdi-par på et objekt og kalder den funktion rekursivt for at krydse objekter med en vilkårlig dybde.

Kom godt i gang

Til denne øvelse skal vi oprette et enkelt Node.js-projekt med en enkelt fil. På din computer skal du vælge en god placering til din fil (f.eks. en projektmappe) og oprette en fil kaldet index.js .

Dernæst skal du sikre dig, at du har installeret Node.js på din computer. Mens koden vi skriver ikke afhænge af, at Node.js fungerer, skal vi bruge det til at køre eller udføre den kode, vi skriver inde i index.js .

Når du har oprettet din fil og Node.js installeret, er vi klar til at komme i gang.

Oprettelse af en funktion til at matche objekter efter nøgle og værdi

En nem måde at forstå begrebet rekursion på er at tænke på en vindeltrappe i et hus. For at gå fra toppen af ​​trappen til bunden skal du gå ned et trin ad gangen.

Selvom du gør det automatisk, har du teknisk set en "funktion" i din hjerne, der fortæller dig, hvordan du går ned et trin ad gangen, indtil du når bunden. Den "funktion" kalder du for hvert trin i trappen, indtil der ikke er flere trin. Når du går ned, fortæller du "funktionen" om at kalde sig selv igen, hvis der er et trin efter det nuværende.

Sådan fungerer rekursion i JavaScript (eller et hvilket som helst programmeringssprog). Du skriver en funktion, der udfører en opgave, og får den funktion til at kalde sig selv igen, hvis den ikke har opfyldt nogle krav – for eksempel at finde en indlejret værdi eller nå slutningen af ​​en liste.

Til denne øvelse skal vi skrive en funktion, der fokuserer på førstnævnte:at finde et indlejret objekt. Mere specifikt ønsker vi at skrive en rekursiv funktion, der finder et indlejret objekt, der indeholder en bestemt nøgle med en bestemt værdi.

Lad os først oprette vores basisfunktion og forklare, hvad den går ud på:

/index.js

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  // We'll implement our function here...
};

Vores funktion vil tage tre argumenter:en object at krydse, en keyToMatch inden for det objekt, og en valueToMatch inden for det objekt.

/index.js

const isObject = (value) => {
  return !!(value && typeof value === "object" && !Array.isArray(value));
};

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  if (isObject(object)) {
    // We'll work on finding our nested object here...
  }

  return null;
};

Dernæst, for at undgå runtime fejl, i brødteksten af ​​vores findNestedObject funktion, tilføjer vi en if sætning med et kald til en ny funktion, vi har tilføjet ovenfor isObject() , der passerer object argument, der blev sendt til findNestedObject .

Ser på isObject() , vil vi være sikre på, at det objekt, vi krydser, faktisk er et objekt. For at finde ud af det, skal vi bekræfte, at den beståede value er ikke null eller udefineret, har en typeof "objekt", og det er ikke et array. Det sidste ser måske mærkeligt ud. Vi skal gøre !Array.isArray() fordi i JavaScript, Array s har en typeof "objekt" (hvilket betyder, at vores tidligere typeof value === "object" test kan "narres" af en række bestået).

Forudsat at isObject() returnerer true for den værdi, vi passerede den, kan vi begynde at krydse objektet. Hvis ikke, som et alternativ fra vores findNestedObject() funktion returnerer vi null for at markere, at vi ikke find et match.

/index.js

const isObject = (value) => {
  return !!(value && typeof value === "object" && !Array.isArray(value));
};

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  if (isObject(object)) {
    const entries = Object.entries(object);

    for (let i = 0; i < entries.length; i += 1) {
      const [treeKey, treeValue] = entries[i];

      if (treeKey === keyToMatch && treeValue === valueToMatch) {
        return object;
      }
    }
  }

  return null;
};

Tilføjelse af noget kompleksitet, nu vil vi begynde at krydse vores objekt. Med "traverse" mener vi at løkke over hvert nøgle/værdi-par på object videregivet til findNestedObject() .

For at udføre den løkke ringer vi først til Object.entries() indlevering af vores object . Dette vil returnere os et array af arrays, hvor hvert array indeholder key af nøgle/værdi-parret, der aktuelt går over som det første element og value af nøgle/værdi-parret, der i øjeblikket loopes over som det andet element. Sådan:

const example = {
  first: 'thing',
  second: 'stuff',
  third: 'value',
};

Object.entries(example);

[
  ['first', 'thing'],
  ['second', 'stuff'],
  ['third', 'value']
]

Dernæst tilføjer vi en for med vores række af nøgle/værdi-par (indgange) loop for at iterere over arrayet. Her i vil være lig med indekset for det aktuelle nøgle/værdi-par, vi går over. Vi vil gerne gøre det, indtil vi har sløjfet alle helheder, så vi siger "kør denne sløjfe mens i < entries.length og for hver iteration, og 1 til det aktuelle indeks i ."

Inde i for sløjfe, bruger vi JavaScript-array-destrukturering til at få adgang til det aktuelle nøgle/værdi-par-array (angivet med entries[i] ), tildeler hver en variabel. Her tildeler vi det første element til variablen objectKey og det andet element til variablen objectValue .

Husk:vores mål er at finde et objekt med den beståede keyToMatch og valueToMatch . For at finde et match, skal vi tjekke hver nøgle og værdi på vores object for at se, om de er et match. Her, forudsat at vi finder et match, returnerer vi object da det opfyldte kravet om at have keyToMatch og valueToMatch .

Tilføjelse af rekursion for at krydse objekter med en vilkårlig dybde

Nu til den sjove del. Lige nu kan vores funktion kun loop over et enkelt-niveau dybde objekt. Det er fantastisk, men husk, vi vil søge efter en indlejret objekt. Fordi vi ikke ved, hvor det objekt kan være i "træet" (et kaldenavn, du lejlighedsvis vil høre for et objekt med indlejrede objekter), skal vi være i stand til at "fortsætte", hvis en af ​​værdierne i nøglen/ værdipar er i sig selv et objekt.

Det er her vores rekursion kommer ind.

/index.js

const isObject = (value) => {
  return !!(value && typeof value === "object" && !Array.isArray(value));
};

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  if (isObject(object)) {
    const entries = Object.entries(object);

    for (let i = 0; i < entries.length; i += 1) {
      const [objectKey, objectValue] = entries[i];

      if (objectKey === keyToMatch && objectValue === valueToMatch) {
        return object;
      }

      if (isObject(objectValue)) {
        const child = findNestedObject(objectValue, keyToMatch, valueToMatch);

        if (child !== null) {
          return child;
        }
      }
    }
  }

  return null;
};

Husk vores trappeanalogi fra tidligere. På dette tidspunkt har vi kun gået et trin ned. For at gå ned til næste trin, skal vi fortælle vores funktion at kalde sig selv igen.

I dette tilfælde ved vi, at der er et andet "trin" eller et objekt, der skal gennemgås, hvis vi passerer objectValue til isObject() funktion, vi satte op tidligere, returnerer true . Hvis det gør , det betyder, at vi skal tjekke, om det objektet indeholder keyToMatch og valueToMatch vi leder efter.

For at krydse det objekt skal vi rekursivt (det vil sige at kalde den funktion, vi i øjeblikket er inde i igen) og sende objectValue ind. sammen med den originale keyToMatch og keyToValue (det, vi leder efter, har ikke ændret sig, kun det objekt, vi vil se på).

Hvis vores rekursive opkald finder et match (hvilket betyder vores rekursive opkald til findNestedObject() gør ikke returner null ), returnerer vi det objekt child . Forudsat at vores rekursive opkald til findNestedObject() ikke returnerede en kamp, ​​ville vores traversering stoppe. Hvis vores barn selv havde indlejrede objekter (i overensstemmelse med vores analogi, endnu et "trin" at gå ned), ville vi igen ringe til findNestedObject() .

Fordi denne kode er rekursiv, vil den køre, indtil den enten finder et matchende objekt eller udtømmer de tilgængelige indlejrede objekter til at søge.

Nu til en test. Lad os prøve at finde objektet i dette træ med en name felt lig med "Hernede!"

/index.js

const isObject = (value) => {
  return !!(value && typeof value === "object" && !Array.isArray(value));
};

const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
  if (isObject(object)) {
    const entries = Object.entries(object);

    for (let i = 0; i < entries.length; i += 1) {
      const [objectKey, objectValue] = entries[i];

      if (objectKey === keyToMatch && objectValue === valueToMatch) {
        return object;
      }

      if (isObject(objectValue)) {
        const child = findNestedObject(objectValue, keyToMatch, valueToMatch);

        if (child !== null) {
          return child;
        }
      }
    }
  }

  return null;
};

const staircase = {
  step: 5,
  nextStep: {
    step: 4,
    nextStep: {
      step: 3,
      nextStep: {
        step: 2,
        nextStep: {
          name: "Down here!",
          step: 1,
        },
      },
    },
  },
};

const match = findNestedObject(staircase, "name", "Down here!");
console.log(match);
// { name: "Down here!", step: 1 }

const match2 = findNestedObject(staircase, "step", 3);
console.log(match2);
// { step: 3, nextStep: { step: 2, nextStep: { name: "Down here!", step: 1 } } }

Her er en hurtig demo af dette, der kører i realtid:

Afslutning

I denne øvelse lærte vi, hvordan man rekursivt krydser et objekt ved hjælp af JavaScript. Vi lærte, hvordan man opretter en basisfunktion, der var i stand til at sløjfe over tasterne på et objekt, vi passerede det, og ledte efter et matchende nøgle- og værdipar. Derefter lærte vi, hvordan man bruger denne funktion rekursivt , kalder det inde fra sig selv, hvis værdien af ​​nøgle/værdi-parret, vi aktuelt løb over, var et objekt.