Shallow & Deep copy

김병엽·2024년 7월 29일

프로그래밍 언어에는 컴퓨터 메모리에 데이터를 저장하는 두 가지 공간, 스택 과 힙이 존재한다.

스택 은 지역 기본 변수와 객체에 대한 참조를 저장하는 임시 저장 메모리이다.

은 전역 변수를 저장한다.
객체 값은 힙에 저장되고 스택은 해당 값에 대한 참조(포인터)만 포함한다.

객체를 생성하면 스택에 두 개의 포인터(참조)가 생성되고 힙에는 하나의 값만 생성된다.

객체 변수를 만들면 메모리는 값 자체가 아니라 값의 실제 위치에 대한 "주소"를 저장한다.

같은 값을 가리키는 두 개의 참조를 수신하며 이것이 객체 중 하나를 변형하면 항상 두 객체가 모두 변경되는 이유이다.

또한 이것이 복사본이 필요한 이유이다.


Shallow copy

얕은 복사를 생성하려면 다음 방법을 사용할 수 있다.

  • 스프레드 구문[…] {…}
  • Object.assign()
  • Array.from()
  • Object.create()
  • Array.prototype.concat()

spread syntax

const numbers = [1, 2, 3, 4, 5];
const _numbers = [...numbers];

_numbers[0] = 10;

console.log(numbers); 
console.log(_numbers); 
// [1, 2, 3, 4, 5]
// [10, 2, 3, 4, 5]

concat()

const numbers1 = [1, 2, 3, 4];
const numbers2 = [5, 6, 7, 8];

const numbersAll = numbers1.concat(numbers2);

console.log(numbers1); // [1, 2, 3, 4];
console.log(numbersAll); // [1, 2, 3, 4, 5, 6, 7, 8];

assign()

const targetObj = {a: 1, b: 2};
const sourceObj = {b: 3, c: 4};

const result = Object.assign(targetObj, sourceObj);

console.log(targetObj); // {a: 1, b: 3, c: 4}
console.log(result); // {a: 1, b: 3, c: 4}
console.log(source0bj); // {b: 3, c: 4}

Array.from()

const set = new Set(['a', 'b', 'c', 'a']); 
const array = Array.from(set);

console.log(set); // {'a', 'b', 'c'} 
console.log(array); // ['a', 'b', 'c']

create()

const language = {name: "JavaScript", age: 26};
const _language = Object.create(language);
_language.age = 200;

console.log(language);
//{name: "JavaScript", age: 26};

console.log(_language);
//{name: "JavaScript", age: 200};

❗️중첩된 객체의 경우 얕은 복사 의 문제가 발생.

const language = ["JavaScript", {age: 26, creator: "Brendan Eich"}]; 
const language = [...language];

_language[1].age = 126;

console.log(language);
console.log(_language);

// ["JavaScript", {age: 126, creator: "Brendan Eich"}] 
// ["JavaScript", {age: 126, creator: "Brendan Eich"}]

복사본을 변경하면 소스 값도 변경된다.

이러한 이유는 공유 참조(둘 다 같은 값을 가리킴) 때문이다.

이 문제를 해결하기 위한 Deep copy가 있다.


Deep copy

심층 복사를 생성하려면 다음을 사용할 수 있다.

  • JSON.parse(JSON.stringify())
  • structuredClone()
  • Lodash 와 같은 타사 라이브러리
const language = ["JavaScript", {age: 26, creator: "Brendan Eich"}];
const _language = JSON.parse(JSON.stringify(language));
const __language = structuredClone(language);

_language[1].age = 126;
__language[1].age = 1;

console.log(language); 
console.log(_language);
console.log(__language);

// ["JavaScript", {age: 26, creator: "Brendan Eich"}] 
// ["JavaScript", {age: 126, creator: "Brendan Eich"}] 
// ["JavaScript", {age: 1, creator: "Brendan Eich"}]

이런 방식으로 복사본을 만들면 문제가 해결되고 모든 변형은 직접 변경된 객체에만 적용된다.

이는 복사본이 더 이상 소스 객체와 참조를 공유하지 않기 때문이다. 모두 별도의 값이다.

Deep copy하는 첫 번째 방법은 JSON.stringify() 메서드를 사용하여 JSON 문자열로 변환한 다음 JSON.parse()를 사용하여 객체로 다시 되돌리는 것이다.

이런 식으로 소스 객체와 공유 참조가 없는 완전히 새로운 객체를 받는다. 하지만 이 메서드는 직렬화 가능한 객체에만 사용할 수 있으며, 예를 들어 함수, 심볼, 재귀적 데이터에는 사용할 수 없다.

다른 메서드인 structuredClone() 은 직렬화 가능한 객체를 복제하는 훌륭한 대안이다. 두 번째 선택적 인수를 사용하면 원래 값을 새 객체로 전송할 수 있다. 즉, 전송된 데이터는 소스 객체에서 분리되어 더 이상 액세스할 수 없다.

structuredClone()은 브라우저의 기능이지 JavaScript 자체가 아닙니다.


Conclusion

얕은 복사는 소스 객체와 참조를 공유하며, 중첩되지 않은 객체에 더 적합하며 JavaScript로 프로그래밍할 때 일반적으로 사용된다.

하지만 중첩된 객체를 처리해야 하거나 어떤 값을 받는지 모르는 경우(예: API에서) 대신 깊은 복사를 사용해야 한다.

깊은 복사 는 원본 객체와 참조를 공유하지 않기 때문이다.


Reference

makimo

profile
선한 영향력을 줄 수 있는 개발자가 되자, 되고싶다.

0개의 댓글