[JavaScript] 얕은 복사와 깊은 복사

Februaar·2024년 6월 24일
3

JavaScript

목록 보기
2/6
post-thumbnail

자바스크립트를 처음 접했을 때, 얕은 복사와 깊은 복사의 개념을 여러 번 읽어봤지만, 이해가 잘 되지 않았다. 지금은 이런 기초적인 개념을 모르는 것은 말이 안된다고 생각하지만, 막상 누군가가 물어보면 제대로 설명하지 못하고 당황할 때가 많다. 그래서 면접 준비를 위해 이 개념들을 명확하게 정리해야겠다고 생각했다.


💡 기본형 데이터 복사와 비교

기본형 데이터를 복사하면 실제 값이 복사된다.

let num1 = 10;
let num2 = num1; // num1의 값을 num2에 복사

console.log(num1); // 10
console.log(num2); // 10

기본형인 숫자를 복사했을 때, num1의 값인 10이 num2에 복사된다. 여기서 중요한 점은 num1num2가 서로 다른 메모리 공간을 차지한다는 점이다.

num2 = 20; // num2의 값을 변경

console.log(num1); // 10
console.log(num2); // 20

위 예시에서 num2 변수에 20이라는 값을 할당하게 되면, 메모리 영역에서 새로운 공간을 확보해 20이라는 값을 저장한다. 이로써 num1num2는 서로 독립적인 값을 가지게 되고, 결과적으로 num1의 값은 여전히 10이고, num2의 값은 20이 된다.

💡 참조형 데이터 복사와 비교

참조형 데이터를 복사하면 실제 데이터가 아니라 데이터의 참조가 복사된다.

let obj1 = { name: "Star", age: 28 };
let obj2 = obj1; // obj1의 참조를 obj2에 복사

console.log(obj1); // { name: "Star", age: 28 }
console.log(obj2); { name: "Star", age: 28 }

참조형인 객체 obj1obj2에 복사할 때, obj2obj1이 가리키는 동일한 객체를 참조하게 된다. 즉, obj1obj2는 같은 객체를 가리키고 있다는 말이다.

obj2.age = 30; // obj2를 통해 객체의 age 속성 값을 변경

console.log(obj1); // { name: "Star", age: 30 }
console.log(obj2); // { name: "Star", age: 30 }

위 예시에서 obj2age 값을 변경하면, obj1age 값도 변경된다. 왜냐하면 obj1obj2가 동일한 객체를 참조하고 있기 때문이다. 따라서 한 쪽의 변경이 다른 쪽에도 영향을 미친다.


앞에서도 언급했지만 참조형 데이터를 복사할 때, 단순히 참조를 복사하는 경우 원본과 복사본이 같은 객체를 가리키게 되어, 하나를 변경하면 다른 하나도 변경되는 의도치 않은 동작이 발생할 수 있다고 했다.

그렇다면 개발자가 의도하지 않은 동작이 발생하지 않도록 하기 위해 사전에 어떤 이해가 필요할까? 라는 생각에 꼬리를 물어 이러한 문제점을 해결하기 위해 얕은 복사와 깊은 복사의 개념에 대한 이해가 필요하다는 생각이 들었다.



💡 얕은 복사와 깊은 복사

얕은 복사 - 바로 아래 단계의 값만 복사하는 방법

깊은 복사 - 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법

위와 같이 정리해볼 수 있지만 아직 명확한 차이가 느껴지지 않는다. 복사 방법을 복사하려는 대상인 데이터가 중첩되어 있을 때와 중첩되지 않았을 때의 예시를 통해 생각하면 이해하기 조금 더 쉬울 것 같다.


📌 중첩되지 않은 객체

  • 얕은 복사: 중첩되지 않은 객체에서도 만 복사하므로, 원본과 사본은 서로 독립적 이다. 즉, 원본이나 사본 중 하나를 변경해도 다른 쪽에는 영향을 미치지 않는다.
let originalObject = { a: 1, b: 2 };
let shallowCopy = { ...originalObject }; // 얕은 복사
shallowCopy.a = 3;

console.log(originalObject);  // { a: 1, b: 2 }
console.log(shallowCopy);     // { a: 3, b: 2 }
  • 깊은 복사: 깊은 복사를 수행해도 중첩되지 않은 객체에서는 얕은 복사와 결과가 동일하다. 원본과 사본이 독립적이기 때문이다.
let originalObject = { a: 1, b: 2 };
let deepCopy = JSON.parse(JSON.stringify(originalObject)); // 깊은 복사
deepCopy.a = 3;

console.log(originalObject);  // { a: 1, b: 2 }
console.log(deepCopy);        // { a: 3, b: 2 }

📌 중첩된 객체

  • 얕은 복사: 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사한다. 따라서 원본과 사본이 동일한 참조형 데이터의 주소를 가리키게 된다. 즉, 사본을 바꾸면 원본도 바뀌고, 원본을 바꾸면 사본도 바뀌게 된다.
let originalObject = { a: 1, b: { c: 2 } };
let shallowCopy = { ...originalObject }; // 얕은 복사
shallowCopy.b.c = 3;

console.log(originalObject);  // { a: 1, b: { c: 3 } }
console.log(shallowCopy);     // { a: 1, b: { c: 3 } }
  • 깊은 복사: 중첩된 객체의 모든 레벨의 값들을 재귀적으로 복사하여 완전히 독립적인 복사본을 만든다. 따라서 깊은 복사본을 변경해도 원본에는 영향을 미치지 않는다.
let originalObject = { a: 1, b: { c: 2 } };
let deepCopy = JSON.parse(JSON.stringify(originalObject)); // 깊은 복사
deepCopy.b.c = 3;

console.log(originalObject);  // { a: 1, b: { c: 2 } }
console.log(deepCopy);        // { a: 1, b: { c: 3 } }


여기까지 이해한 바로는 중첩된 객체에서 얕은 복사를 했을 때 유일하게 예상치 못한 동작이 발생할 수 있다. 이는 얕은 복사가 객체의 첫 번째 레벨까지만 복사하고, 내부 객체들은 참조만 복사하기 때문이다.

그러니까 어떤 객체를 복사할 때 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체의 속성 중에서 기본형 데이터가 있다면 값 자체를 복사하면 되기 때문에, 그대로 복사 하면 되지만 참조형 데이터의 경우에는 메모리 주소를 참조하고 있기 대문에, 그 값 자체가 아닌 그 값이 기리키는 객체를 복사 해야 한다.

이때 참조형 데이터 안에 또 다른 참조형 데이터가 있을 수 있는데, 이 경우 그 내부 프로퍼티들까지 재귀적으로 복사해야 한다. 그래야만 비로소 깊은 복사가 이루어진다.


✨ 용어 정리

깊은 복사에 대해 공부하다보니 재귀적 이라는 말이 자주 등장하기에 찾아보았다.

  • 재귀 함수
    프로그래밍에서 함수 내부에서 자기 자신을 호출하는 것을 의미.

  • 깊은 복사에서 "재귀적"
    깊은 복사를 할 때 재귀적 이라는 말의 의미는 객체의 속성 중에 참조형 데이터가 있을 경우, 그 참조형 데이터를 복사하는 과정에서 다시 그 참조형 데이터의 속성들까지 복사하는 과정을 반복한다는 뜻이다.


드디어 다음 장으로 넘어가게 되어 설레기도 한다. 데이터 타입 장을 며칠 동안 붙잡고 있어서 살짝 지루해지던 참이었는데, 다행이다 :)

profile
짱개발자가 되기 위한 개발기록 🐯

0개의 댓글