de Lukas Gisder-Dubé

Cum să diferențiem copii adânci și superficiale în JavaScript

Cum sa diferentiem copii adanci si superficiale in JavaScript
Fotografie de Oliver Zenglein pe Unsplash

Nou este întotdeauna mai bun!

Cu siguranță v-ați mai ocupat de copii în JavaScript, chiar dacă nu știați acest lucru. Poate ați auzit și de paradigma din programarea funcțională că nu ar trebui să modificați niciun fel de date existente. Pentru a face acest lucru, trebuie să știți cum să copiați în siguranță valorile în JavaScript. Astăzi, ne vom uita cum să facem acest lucru evitând în același timp capcanele!

În primul rând, ce este o copie?

O copie seamănă cu vechiul lucru, dar nu este. Când schimbați copia, vă așteptați ca originalul să rămână același, în timp ce copia se schimbă.

În programare, stocăm valori în variabile. Efectuarea unei copii înseamnă că inițiați o nouă variabilă cu aceeași valoare. Cu toate acestea, există o mare capcană potențială de luat în considerare: copiere profundă vs. copiere superficială. O copie profundă înseamnă că toate valorile noii variabile sunt copiate și deconectat de original variabil. O copie superficială înseamnă că anumite (sub) valori sunt încă conectat la variabila originală.

Pentru a înțelege cu adevărat copierea, trebuie să intrați în modul în care JavaScript stochează valorile.

Tipuri de date primitive

Tipurile de date primitive includ următoarele:

  • Număr – de ex 1
  • Șir – de ex 'Hello'
  • Boolean – de ex true
  • undefined
  • null

Când creați aceste valori, acestea sunt strâns cuplate cu variabila la care sunt atribuite. Ele există o singură dată. Asta înseamnă că nu trebuie să vă faceți griji cu privire la copierea tipurilor de date primitive în JavaScript. Când faceți o copie, aceasta va fi o copie reală. Să vedem un exemplu:

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

Prin executarea b = a , tu faci copia. Acum, când reatribuiți o nouă valoare la b, valoarea a b modificări, dar nu de a.

Tipuri de date compuse – obiecte și tablouri

Din punct de vedere tehnic, tablourile sunt și ele obiecte, deci se comportă în același mod. Le voi trece amândouă în detaliu mai târziu.

Aici devine mai interesant. Aceste valori sunt stocate de fapt o singură dată când sunt instanțiate, iar atribuirea unei variabile doar creează un indicator (referință) la acea valoare.

Acum, dacă facem o copie b = a și modificați o anumită valoare imbricată în b, se schimbă de fapt aeste și valoarea imbricată, din moment ce a și b indică de fapt același lucru. Exemplu:

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

În exemplul de mai sus, am făcut de fapt un copie superficială. Acest lucru este adesea problematic, deoarece ne așteptăm ca vechea variabilă să aibă valorile originale, nu pe cele modificate. Când îl accesăm, uneori primim o eroare. S-ar putea întâmpla să încercați să o depanați pentru o vreme înainte de a găsi eroarea, deoarece mulți dezvoltatori nu înțeleg cu adevărat conceptul și nu se așteaptă ca aceasta să fie eroarea.

1612175947 382 Cum sa diferentiem copii adanci si superficiale in JavaScript
Fotografie de Thomas Millot pe Unsplash

Să aruncăm o privire la modul în care putem face copii ale obiectelor și matricelor.

Obiecte

Există mai multe moduri de a face copii ale obiectelor, în special cu noile specificații JavaScript care se extind și îmbunătățesc.

Operator de răspândire

Introdus cu ES2015, acest operator este pur și simplu minunat, deoarece este atât de scurt și simplu. „Răspândește” toate valorile într-un obiect nou. Îl puteți folosi după cum urmează:

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

De asemenea, îl puteți utiliza pentru a îmbina două obiecte împreună, de exemplu const c = {...a, ...b} .

Obiect.assign

Acest lucru a fost folosit mai ales înainte ca operatorul de răspândire să fie în jur și, practic, face același lucru. Totuși, trebuie să fii atent, ca primul argument din Object.assign() metoda este de fapt modificată și returnată. Deci, asigurați-vă că treceți obiectul la copiere cel puțin ca al doilea argument. În mod normal, ați trece doar un obiect gol ca prim argument pentru a împiedica modificarea oricăror date existente.

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

Capcana: obiecte imbricate

Așa cum am menționat anterior, există o mare avertizare atunci când se tratează copierea obiectelor, care se aplică ambelor metode enumerate mai sus. Când aveți un obiect imbricat (sau matrice) și îl copiați, obiectele imbricate în interiorul acelui obiect nu vor fi copiate, deoarece acestea sunt doar indicatori / referințe. Prin urmare, dacă schimbați obiectul imbricat, îl veți schimba pentru ambele instanțe, ceea ce înseamnă că ați ajunge să faceți un copie superficială din nou. Exemplu: // RĂU EXEMPLU

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

Pentru a face o copie profundă a obiectelor imbricate, ar trebui să luați în considerare acest lucru. O modalitate de a preveni acest lucru este copierea manuală a tuturor obiectelor imbricate:

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

În cazul în care vă întrebați ce să faceți atunci când obiectul are mai multe chei decât numai foods , puteți utiliza întregul potențial al operatorului spread. Când treceți mai multe proprietăți după ...spread , acestea suprascriu valorile originale, de exemplu const b = {...a, foods: {...a.foods}} .

Efectuarea de copii adânci fără gândire

Ce se întâmplă dacă nu știi cât de adânci sunt structurile cuibărite? Poate fi foarte obositor să parcurgi manual obiecte mari și să copiezi manual fiecare obiect imbricat. Există o modalitate de a copia totul fără să te gândești. Tu pur și simplu stringify obiectul tău și parse imediat după:

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

Aici, trebuie să luați în considerare faptul că nu veți putea copia instanțe de clasă personalizate, deci îl puteți utiliza numai atunci când copiați obiecte cu valorile JavaScript native interior.

1612175947 702 Cum sa diferentiem copii adanci si superficiale in JavaScript
Fotografie de Robert Zunikoff pe Unsplash

Matrice

Copierea matricelor este la fel de obișnuită ca și copierea obiectelor. O mulțime de logici din spatele ei sunt similare, deoarece matricele sunt, de asemenea, doar obiecte sub capotă.

Operator de răspândire

Ca și în cazul obiectelor, puteți utiliza operatorul spread pentru a copia o matrice:

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

Funcții matrice – hartă, filtrare, reducere

Aceste metode vor returna o nouă matrice cu toate (sau unele) valori ale celei originale. În timp ce faceți acest lucru, puteți modifica, de asemenea, valorile, ceea ce este foarte util:

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

Alternativ, puteți modifica elementul dorit în timpul copierii:

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.slice

Această metodă este utilizată în mod normal pentru a returna un subset de elemente, începând de la un index specific și opțional terminându-se la un index specific al matricei originale. Atunci când se utilizează array.slice() sau array.slice(0) veți ajunge cu o copie a matricei originale.

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

Matrice imbricate

Similar cu obiectele, folosirea metodelor de mai sus pentru a copia un tablou cu un alt tablou sau obiect din interior va genera un copie superficială. Pentru a preveni acest lucru, utilizați și JSON.parse(JSON.stringify(someArray)) .

BONUS: instanță de copiere a claselor personalizate

Când sunteți deja un profesionist în JavaScript și vă ocupați de funcțiile sau clasele de constructor personalizate, poate doriți să copiați și instanțele acestora.

Așa cum am menționat anterior, nu puteți doar să le strângeți + să le analizați, deoarece veți pierde metodele de clasă. În schimb, ați dori să adăugați un obicei copy metoda de a crea o nouă instanță cu toate valorile vechi. Să vedem cum funcționează:

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

Pentru a trata obiecte și tablouri la care se face referire în instanța dvs., va trebui să vă aplicați abilitățile recent învățate copiere profundă! Voi adăuga doar o soluție finală pentru constructorul personalizat copy metoda de a o face mai dinamică:

Cu acea metodă de copiere, puteți pune oricâte valori doriți în constructorul dvs., fără a fi nevoie să copiați manual totul!

Despre autor: Lukas Gisder-Dubé a cofondat și a condus o startup ca CTO timp de 1 1/2 ani, construind echipa tehnologică și arhitectura. După ce a părăsit startup-ul, a predat programarea în calitate de instructor principal la Ironhack și acum construiește o agenție de startup și consultanță la Berlin. Verifică dube.io pentru a afla mai multe.

Cum sa intelegeti cuvantul cheie acesta si contextul in JavaScript