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.