javascript 얕은복사, 깊은복사에 대한 이해
복사는 값의 전달을 목적으로 합니다. 문서를 복사하는 것처럼 컴퓨터 내에서도 파일을 복사하거나 하죠.
컴퓨터 사용 시 흔하게 파일을 복사합니다. 원본이 있을테고 복사한 파일은 원본과 똑같지만 원본은 아닙니다.
그리고 복사본을 수정한다고 해도 원본은 변하지않고 복사본만 수정되겠죠.
위와 같은 방식이 일반적인 사람들이 익숙해져 있는 사고방식입니다.
실제 생활에서의 복사본은 원본과 연결되지 않습니다. 그리고 이 방식을 깊은복사라고 이해해 두시면 되겠습니다.
얕은복사 전에 참조를 먼저
javascript만 공부하신 분이라면 대략 다음과 같이 얕은 복사를 이해 또는 암기하고 계실겁니다.
객체를 얕은복사를 하면 객체의 원소 중 원시타입이 아닌 객체에 대해 참조를 공유한다.
네 이 말은 맞습니다. 하지만 전 이렇게 설명하는 글을 읽고 이질감이 느껴졌습니다.
왜 이질감이 느껴졌을까?
사실 얕은복사라는 말은 c언어와 가은 다른 언어에서도 있는 개념입니다. 포인터를 사용하여 객체를 참조한다는 의미가 있죠.
얕은복사의 목적에 객체의 참조를 공유 한다는 측면은 맞지만 Container가 되는 원본 객체애 대한 참조에 대한 설명은 하지 않는 설명이기 때문입니다.
컨테이너에 대한 참조
여기에서 컨테이너란 원본 객체 그 자체를 의미하며 다음과 같은 방식으로 객체에 대한 참조를 얻을 수 있습니다.
const obj = {a:1, b:2 , c:{d:7, e:8}}
위 코드같이 javascript에서는 obj
변수에 무언가를 대입하면 이 변수는 값을 참조방식으로 가리킵니다.
위 방식이 자바스크립트나 참조를 사용하는 언어의 기본적인 동작방식입니다.
그렇다면 이런 참조는 어떤 점이 좋을까요?
참조의 좋은 점
const obj = {a:1, b:2 , c:{d:7, e:8}} // 원본
const obj_copy = obj;
obj_copy['d']=99;
console.log(obj); //{ a: 1, b: 2, c: { d: 7, e: 8 }, d: 99 }
console.log(obj_copy); //{ a: 1, b: 2, c: { d: 7, e: 8 }, d: 99 }
참조를 사용하면 원본의 수많은 카피본(=연산으로 생성된)이 생기더라도 원본과 똑같은 객체를 생성할필요가 없습니다. 자바스크립트에서의 대입연산은 이렇게 동작합니다.
우리가 파일복사하는 개념하고는 다르죠. 즉 복사본도 새로운 카피본이 아닌 원본을 가리킨다는 것입니다.
메모리도 아끼고 효율적이겠죠.
얕은복사 시 컨테이너에 대한 참조의 변화
그렇다면 흔히 말하는 얕은복사와 다른 점을 살펴봅시다.
다음은 얕은복사를 수행하는 코드입니다.
const obj = {a:1, b:2 , c:{d:7, e:8}} // 원본
const obj_copy = Object.assign({}, obj); //얕은복사
obj_copy['d']=99;
console.log(obj); // { a: 1, b: 2, c: { d: 7, e: 8 } }
console.log(obj_copy); // { a: 1, b: 2, c: { d: 7, e: 8 }, d:99 }
컨테이너 객체에 대한 참조가 아닌 컨테이너 객체가 복사가 되었습니다.
앞서와 달리 원본 객체에는 obj.d.c
라는 요소는 존재하지 않습니다.
다음과 같은 부분이 변하였습니다.
- 컨테이너 객체 새롭게 생성
- 복사본에 내부 원시타입은 새롭게 생성
위 2가지가 기본적인 참조를 통한 객체의 복사와 얕은복사의 차이점이며 내부 객체요소는 여전히 복사본과 원본이 동일한 참조를 가집니다. 이 부분이 일반적이 설명이며 핵심이지만, 외부 컨테이너가 새롭게 생겼다는 점 역시 중요한 포인트입니다.
깊은복사
이런 방식으로 깊은복사도 이해하시면 됩니다. 깊은 복사는 내부 요소 모두 파일복사하듯 복사하는 것입니다.
const obj = {a:1, b:2 , c:{d:7, e:8}} // 원본
const obj_copy = {...obj, c:{...obj.c}}
obj_copy['d']=99;
console.log(obj); // { a: 1, b: 2, c: { d: 7, e: 8 } }
console.log(obj_copy); // { a: 1, b: 2, c: { d: 7, e: 8 }, d: 99 }
이 코드에 다음과 같은 코드를 추가하면
obj_copy['c']['d']='HI';
console.log(obj); //{ a: 3000, b: 2, c: { d: 7, e: 8 }
console.log(obj_copy); //{ a: 3000, b: 2, c: { d: 'HI', e: 8 }, d: 99 }
내부의 c
객체가 깊은복사가 되어 원본과 복사본이 서로 연관이 없게됩니다.
참고(원시타입에 대한 참고)
이 글을 쓰면서 헷갈렸던 점이 객체 원시타입에 대한 참조입니다. 파이썬의 경우 원시타입도 모두 객체로 다루기 때문에 비슷한 방식의 참조이면서도 위 그림이 달라집니다. 너무 깊에 알 필요는 없지만, 참고로 기록해봅니다.