Како разликовати дубоке и плитке копије у ЈаваСцрипт-у

Ново је увек боље!

Сигурно сте се раније бавили копијама у ЈаваСцрипт-у, чак и ако то нисте знали. Можда сте такође чули за парадигму у функционалном програмирању да не бисте требали мењати постојеће податке. Да бисте то урадили, морате знати како безбедно копирати вредности у ЈаваСцрипт-у. Данас ћемо размотрити како то да урадимо, избегавајући замке!

Пре свега, шта је копија?

Копија само изгледа као стара ствар, али није. Када промените копију, очекујете да оригинална ствар остане иста, док се копија мења.

У програмирању вредности чувамо у променљивим. Копирање значи да иницирате нову променљиву са истим вредностима. Међутим, постоји велика потенцијална замка коју треба размотрити: дубоко копирање насупрот плитком копирању . Дубинска копија значи да су све вредности нове променљиве копиране и одвојене од оригиналне променљиве. Плитка копија значи да су одређене (под) вредности и даље повезане са оригиналном променљивом.

Да бисте заиста разумели копирање, морате да сазнате како ЈаваСцрипт чува вредности.

Примитивни типови података

Примитивни типови података укључују следеће:

  • Број - нпр 1
  • Стринг - нпр 'Hello'
  • Булова вредност - нпр true
  • undefined
  • null

Када креирате ове вредности, оне су уско повезане са променљивом којој су додељене. Они постоје само једном. То значи да заправо не морате да бринете о копирању примитивних типова података у ЈаваСцрипт. Када направите копију, то ће бити права копија. Погледајмо пример:

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

Извршењем b = aправите копију. Сада, када доделите нову вредност b, вредност се bмења, али не и a.

Састављени типови података - објекти и низови

Технички, низови су такође објекти, па се понашају на исти начин. Касније ћу детаљно проћи кроз обојицу.

Овде постаје занимљивије. Ове вредности се заправо чувају само једном када се инстанцирају, а додељивање променљиве само креира показивач (референцу) на ту вредност .

Сада, ако направимо копију b = a, и променити неке угњеждену вредност у b, заправо мења a'с угњеждену вредност, будући aи bзаправо указују на исту ствар. Пример:

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

У горњем примеру смо заправо направили плитку копију . Ово је често проблематично, јер очекујемо да стара променљива има оригиналне вредности, а не промењене. Када му приступимо, понекад добијемо грешку. Може се десити да покушате да отклоните грешке неко време пре него што пронађете грешку, јер многи програмери заправо не схватају концепт и не очекују да је то грешка.

Погледајмо како можемо да направимо копије објеката и низова.

Предмети

Постоји више начина за копирање објеката, посебно са новом проширивањем и побољшањем ЈаваСцрипт спецификације.

Спреад оператор

Представљен са ЕС2015, овај оператер је сјајан, јер је тако кратак и једноставан. Све вредности се „шире“ у нови објекат. Можете га користити на следећи начин:

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

Можете га користити и за спајање два објекта, на пример const c = {...a, ...b}.

Објецт.ассигн

Ово се углавном користило пре него што је оператер ширења био у близини, и у основи ради исту ствар. Морате бити опрезни, јер се први аргумент у Object.assign()методи заправо мења и враћа. Зато обавезно проследите објекат за копирање бар као други аргумент. Уобичајено је да само проследите празан објекат као први аргумент да бисте спречили модификовање постојећих података.

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

Замка: угнежђени предмети

Као што је раније поменуто, постоји једно велико упозорење када се ради о копирању објеката, што се односи на обе горе наведене методе. Када имате угнежђени објект (или низ) и копирате га, угнежђени објекти унутар тог објекта неће се копирати, јер су то само показивачи / референце. Стога, ако промените угнежђени објекат, променићете га за обе инстанце, што значи да бисте на крају поново направили плитку копију . Пример: // ЛОШ ПРИМЕР

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

Да бисте направили дубинску копију угнежђених објеката , морали бисте то узети у обзир. Један од начина да се то спречи је ручно копирање свих угнежђених објеката:

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

У случају да сте се питали шта да радите када објекат има више кључева него само foods, можете искористити пуни потенцијал оператора ширења. Када прослеђују више својстава након ...spread, они преписују оригиналне вредности, на пример const b = {...a, foods: {...a.foods}}.

Прављење дубоких копија без размишљања

Шта ако не знате колико су дубоко угнежђене структуре? Може бити врло заморно ручно пролазити кроз велике објекте и ручно копирати сваки угнежђени објекат. Постоји начин да се све копира без размишљања. Једноставно stringifyставите свој предмет и parseто одмах након:

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

Овде морате узети у обзир да нећете моћи да копирате инстанце прилагођене класе, па га можете користити само када копирате објекте са изворним ЈаваСцрипт вредностима .

Низови

Копирање низова једнако је често као и копирање објеката. Много логике која стоји иза тога је слично, јер су низови такође само објекти испод хаубе.

Спреад оператор

Као и код објеката, и за копирање низа можете користити оператор ширења:

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

Функције низа - мапирање, филтрирање, смањење

Ове методе ће вратити нови низ са свим (или неким) вредностима оригиналног. Док то радите, можете и да измените вредности, што је врло корисно:

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

Можете и да промените жељени елемент током копирања:

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()или array.slice(0)ћете завршити са копијом оригиналног низа.

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

Угњежђени низови

Слично објектима, коришћење горњих метода за копирање низа са другим низом или објектом у њему генерисаће плитку копију . Да бисте то спречили, такође користите JSON.parse(JSON.stringify(someArray)).

БОНУС: копирање инстанце прилагођених класа

Када сте већ професионалац у ЈаваСцрипт-у и бавите се својим прилагођеним конструкторским функцијама или класама, можда желите да копирате и инстанце таквих.

Као што је већ поменуто, не можете их само рашчланити + рашчланити јер ћете изгубити методе класе. Уместо тога, желели бисте да додате прилагођени copyметод за стварање нове инстанце са свим старим вредностима. Да видимо како то функционише:

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

Да бисте се бавили објектима и низовима на које се позива ваша инстанца, мораћете да примените своје ново научене вештине о дубинском копирању ! Само ћу додати коначно решење за copyметод прилагођеног конструктора да би био динамичнији:

Помоћу те методе копирања у свој конструктор можете да унесете онолико вредности колико желите, а да не морате ручно да копирате све!

О аутору: Лукас Гисдер-Дубе суосновао је и водио стартуп као ЦТО током 1 1/2 године, градећи технички тим и архитектуру. Након што је напустио стартуп, предавао је кодирање као водећи инструктор у Иронхацку и сада гради Стартуп агенцију и консултантске услуге у Берлину. Погледајте дубе.ио да бисте сазнали више.