자바스크립트를 처음 접했을 때, 얕은 복사와 깊은 복사의 개념을 여러 번 읽어봤지만, 이해가 잘 되지 않았다. 지금은 이런 기초적인 개념을 모르는 것은 말이 안된다고 생각하지만, 막상 누군가가 물어보면 제대로 설명하지 못하고 당황할 때가 많다. 그래서 면접 준비를 위해 이 개념들을 명확하게 정리해야겠다고 생각했다.
기본형 데이터를 복사하면 실제 값이 복사된다.
let num1 = 10;
let num2 = num1; // num1의 값을 num2에 복사
console.log(num1); // 10
console.log(num2); // 10
기본형인 숫자를 복사했을 때, num1
의 값인 10이 num2
에 복사된다. 여기서 중요한 점은 num1
과 num2
가 서로 다른 메모리 공간을 차지한다는 점이다.
num2 = 20; // num2의 값을 변경
console.log(num1); // 10
console.log(num2); // 20
위 예시에서 num2
변수에 20이라는 값을 할당하게 되면, 메모리 영역에서 새로운 공간을 확보해 20이라는 값을 저장한다. 이로써 num1
과 num2
는 서로 독립적인 값을 가지게 되고, 결과적으로 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 }
참조형인 객체 obj1
를 obj2
에 복사할 때, obj2
는 obj1
이 가리키는 동일한 객체를 참조하게 된다. 즉, obj1
과 obj2
는 같은 객체를 가리키고 있다는 말이다.
obj2.age = 30; // obj2를 통해 객체의 age 속성 값을 변경
console.log(obj1); // { name: "Star", age: 30 }
console.log(obj2); // { name: "Star", age: 30 }
위 예시에서 obj2
의 age
값을 변경하면, obj1
의 age
값도 변경된다. 왜냐하면 obj1
과 obj2
가 동일한 객체를 참조하고 있기 때문이다. 따라서 한 쪽의 변경이 다른 쪽에도 영향을 미친다.
앞에서도 언급했지만 참조형 데이터를 복사할 때, 단순히 참조를 복사하는 경우 원본과 복사본이 같은 객체를 가리키게 되어, 하나를 변경하면 다른 하나도 변경되는 의도치 않은 동작이 발생할 수 있다고 했다.
그렇다면 개발자가 의도하지 않은 동작이 발생하지 않도록 하기 위해 사전에 어떤 이해가 필요할까? 라는 생각에 꼬리를 물어 이러한 문제점을 해결하기 위해 얕은 복사와 깊은 복사의 개념에 대한 이해가 필요하다는 생각이 들었다.
얕은 복사 - 바로 아래 단계의 값만 복사하는 방법
깊은 복사 - 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법
위와 같이 정리해볼 수 있지만 아직 명확한 차이가 느껴지지 않는다. 복사 방법을 복사하려는 대상인 데이터가 중첩되어 있을 때와 중첩되지 않았을 때의 예시를 통해 생각하면 이해하기 조금 더 쉬울 것 같다.
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 } }
여기까지 이해한 바로는 중첩된 객체에서 얕은 복사를 했을 때 유일하게 예상치 못한 동작이 발생할 수 있다. 이는 얕은 복사가 객체의 첫 번째 레벨까지만 복사하고, 내부 객체들은 참조만 복사하기 때문이다.
그러니까 어떤 객체를 복사할 때 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체의 속성 중에서 기본형 데이터가 있다면 값 자체를 복사하면 되기 때문에, 그대로 복사 하면 되지만 참조형 데이터의 경우에는 메모리 주소를 참조하고 있기 대문에, 그 값 자체가 아닌 그 값이 기리키는 객체를 복사 해야 한다.
이때 참조형 데이터 안에 또 다른 참조형 데이터가 있을 수 있는데, 이 경우 그 내부 프로퍼티들까지 재귀적으로 복사해야 한다. 그래야만 비로소 깊은 복사가 이루어진다.
깊은 복사에 대해 공부하다보니 재귀적 이라는 말이 자주 등장하기에 찾아보았다.
재귀 함수
프로그래밍에서 함수 내부에서 자기 자신을 호출하는 것을 의미.
깊은 복사에서 "재귀적"
깊은 복사를 할 때 재귀적 이라는 말의 의미는 객체의 속성 중에 참조형 데이터가 있을 경우, 그 참조형 데이터를 복사하는 과정에서 다시 그 참조형 데이터의 속성들까지 복사하는 과정을 반복한다는 뜻이다.
드디어 다음 장으로 넘어가게 되어 설레기도 한다. 데이터 타입 장을 며칠 동안 붙잡고 있어서 살짝 지루해지던 참이었는데, 다행이다 :)