Hvordan man skelner mellem dybe og lave kopier i JavaScript

Nyt er altid bedre!

Du har helt sikkert behandlet kopier i JavaScript før, selvom du ikke vidste det. Måske har du også hørt om paradigmet inden for funktionel programmering, at du ikke skal ændre eksisterende data. For at gøre det skal du vide, hvordan du sikkert kopierer værdier i JavaScript. I dag skal vi se på, hvordan man gør dette, mens man undgår faldgruber!

Først og fremmest, hvad er en kopi?

En kopi ligner bare den gamle ting, men er den ikke. Når du ændrer kopien, forventer du, at den originale ting forbliver den samme, mens kopien ændres.

I programmeringen gemmer vi værdier i variabler. At lave en kopi betyder, at du starter en ny variabel med samme værdi (er). Der er dog en stor potentiel faldgrube at overveje: dyb kopiering versus lav kopiering . En dyb kopi betyder, at alle værdierne for den nye variabel kopieres og frakobles fra den oprindelige variabel. En lav kopi betyder, at visse (under-) værdier stadig er forbundet med den oprindelige variabel.

For virkelig at forstå kopiering skal du komme ind på, hvordan JavaScript gemmer værdier.

Primitive datatyper

Primitive datatyper inkluderer følgende:

  • Antal - f.eks 1
  • Streng - f.eks 'Hello'
  • Boolsk - f.eks true
  • undefined
  • null

Når du opretter disse værdier, er de tæt forbundet med den variabel, de er tildelt. De eksisterer kun én gang. Det betyder, at du ikke rigtig behøver at bekymre dig om at kopiere primitive datatyper i JavaScript. Når du laver en kopi, vil det være en rigtig kopi. Lad os se et eksempel:

const a = 5
let b = a // this is the copy
b = 6
console.log(b) // 6
console.log(a) // 5

Ved at udføre b = alaver du kopien. Nu, når du tildeler en ny værdi til b, ændres værdien b, men ikke af a.

Sammensatte datatyper - Objekter og arrays

Teknisk set er arrays også objekter, så de opfører sig på samme måde. Jeg vil gennemgå dem begge detaljeret senere.

Her bliver det mere interessant. Disse værdier gemmes faktisk kun en gang, når de instantieres, og tildeling af en variabel skaber bare en markør (reference) til den værdi .

Nu, hvis vi laver en kopi b = aog ændrer en indlejret værdi i b, ændrer den faktisk også aden indlejrede værdi, da aog bfaktisk peger på den samme ting. Eksempel:

const a = {
 en: 'Hello',
 de: 'Hallo',
 es: 'Hola',
 pt: 'Olà'
}
let b = a
b.pt = 'Oi'
console.log(b.pt) // Oi
console.log(a.pt) // Oi

I eksemplet ovenfor lavede vi faktisk en lav kopi . Dette er ofte gange problematisk, da vi forventer, at den gamle variabel har de oprindelige værdier, ikke de ændrede. Når vi får adgang til det, får vi nogle gange en fejl. Det kan ske, at du prøver at debugge det et stykke tid, før du finder fejlen, da mange udviklere ikke rigtig fatter begrebet og ikke forventer, at det er fejlen.

Lad os se på, hvordan vi kan lave kopier af objekter og arrays.

Objekter

Der er flere måder at lave kopier af objekter, især med den nye udvidelse og forbedring af JavaScript-specifikationen.

Spred operatør

Introduceret med ES2015 er denne operatør bare fantastisk, fordi den er så kort og enkel. Det 'spreder' alle værdier ud i et nyt objekt. Du kan bruge det som følger:

const a = {
 en: 'Bye',
 de: 'Tschüss'
}
let b = {...a}
b.de = 'Ciao'
console.log(b.de) // Ciao
console.log(a.de) // Tschüss

Du kan også bruge den til at flette to objekter sammen, for eksempel const c = {...a, ...b}.

Objekt.tildeling

Dette blev for det meste brugt, før spredningsoperatøren var rundt, og det gør dybest set det samme. Du skal dog være forsigtig, da det første argument i Object.assign()metoden faktisk bliver ændret og returneret. Så sørg for at du sender objektet til at kopiere mindst som det andet argument. Normalt ville du bare videregive et tomt objekt som det første argument for at forhindre ændring af eksisterende data.

const a = {
 en: 'Bye',
 de: 'Tschüss'
}
let b = Object.assign({}, a)
b.de = 'Ciao'
console.log(b.de) // Ciao
console.log(a.de) // Tschüss

Fallgrube: Indlejrede genstande

Som nævnt før er der en stor advarsel, når man beskæftiger sig med kopiering af objekter, hvilket gælder for begge ovennævnte metoder. Når du har et indlejret objekt (eller et array), og du kopierer det, kopieres indlejrede objekter inde i dette objekt ikke, da de kun er henvisninger / referencer. Derfor, hvis du ændrer det indlejrede objekt, vil du ændre det i begge tilfælde, hvilket betyder at du ender med at lave en lav kopi igen . Eksempel: // DÅRLIG EKSEMPEL

const a = {
 foods: {
 dinner: 'Pasta'
 }
}
let b = {...a}
b.foods.dinner = 'Soup' // changes for both objects
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Soup

For at lave en dyb kopi af indlejrede objekter skal du overveje det. En måde at forhindre det på er manuel kopiering af alle indlejrede objekter:

const a = {
 foods: {
 dinner: 'Pasta'
 }
}
let b = {foods: {...a.foods}}
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Hvis du spekulerede på, hvad du skal gøre, når objektet har flere nøgler end kun foods, kan du bruge spredningsoperatørens fulde potentiale. Når du overfører flere egenskaber efter ...spread, overskriver de f.eks const b = {...a, foods: {...a.foods}}. De oprindelige værdier .

At lave dybe kopier uden at tænke over

Hvad hvis du ikke ved, hvor dybt de indlejrede strukturer er? Det kan være meget kedeligt at manuelt gennemgå store objekter og kopiere hvert indlejrede objekt manuelt. Der er en måde at kopiere alt uden at tænke over. Du simpelthen stringifydit objekt og parsedet lige efter:

const a = {
 foods: {
 dinner: 'Pasta'
 }
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Her skal du overveje, at du ikke vil være i stand til at kopiere brugerdefinerede klasseinstanser, så du kan kun bruge den, når du kopierer objekter med native JavaScript-værdier indeni.

Arrays

Kopiering af arrays er lige så almindeligt som at kopiere objekter. Meget af logikken bag det er ens, da arrays også bare er genstande under emhætten.

Spred operatør

Som med objekter kan du bruge spredningsoperatoren til at kopiere et array:

const a = [1,2,3]
let b = [...a]
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

Array-funktioner - kortlæg, filtrer, reducer

Disse metoder returnerer et nyt array med alle (eller nogle) værdier for den oprindelige. Mens du gør det, kan du også ændre værdierne, hvilket kommer meget nyttigt:

const a = [1,2,3]
let b = a.map(el => el)
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

Alternativt kan du ændre det ønskede element under kopiering:

const a = [1,2,3]
const b = a.map((el, index) => index === 1 ? 4 : el)
console.log(b[1]) // 4
console.log(a[1]) // 2

Array.skive

Denne metode bruges normalt til at returnere en delmængde af elementerne, startende ved et specifikt indeks og eventuelt slutter ved et specifikt indeks i den oprindelige matrix. Når du bruger array.slice()eller array.slice(0)vil du ende op med en kopi af den originale array.

const a = [1,2,3]
let b = a.slice(0)
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

Indlejrede arrays

Svarende til objekter, ved hjælp af ovenstående metoder til at kopiere en matrix med en anden matrix eller et objekt indeni, genereres en lav kopi . For at forhindre det skal du også bruge JSON.parse(JSON.stringify(someArray)).

BONUS: kopiinstans af brugerdefinerede klasser

Når du allerede er pro i JavaScript, og du beskæftiger dig med dine brugerdefinerede konstruktorfunktioner eller klasser, vil du måske også kopiere forekomster af dem.

Som nævnt før kan du ikke bare strenge + parse dem, da du mister dine klassemetoder. I stedet for vil du tilføje en tilpasset copymetode til at oprette en ny instans med alle de gamle værdier. Lad os se, hvordan det fungerer:

class Counter {
 constructor() {
 this.count = 5
 }
 copy() {
 const copy = new Counter()
 copy.count = this.count
 return copy
 }
}
const originalCounter = new Counter()
const copiedCounter = originalCounter.copy()
console.log(originalCounter.count) // 5
console.log(copiedCounter.count) // 5
copiedCounter.count = 7
console.log(originalCounter.count) // 5
console.log(copiedCounter.count) // 7

For at håndtere objekter og arrays, der er refereret inde i din instans, skal du anvende dine nyligt lærte færdigheder om dybkopiering ! Jeg vil bare tilføje en endelig løsning til den tilpassede konstruktormetode for copyat gøre den mere dynamisk:

Med denne kopimetode kan du placere så mange værdier som du vil i din konstruktør uden at skulle kopiere alt manuelt!

Om forfatteren: Lukas Gisder-Dubé var medstifter af og ledede en opstart som CTO i 1 1/2 år og byggede teknologiteamet og arkitekturen. Efter at have forladt opstarten lærte han kodning som Lead Instructor hos Ironhack og bygger nu et Startup Agency & Consultancy i Berlin. Tjek dube.io for at lære mere.