JS 공부를 하고 질의응답을 하던 중 깊은 복사와 얕은 복사의 특징에 대해서 잘 이해하고 있지 못하다는 느낌을 받았다.
그래서 얕은 복사와 깊은 복사의 예제를 공부하면서 공부해보려고 한다.
1. 얕은 복사
복사본의 속성이 복사본이 만들어진 원본 객체와 같은 참조 (메모리 내의 같은 값을 가리킴)를 공유하는 복사
위 정의가 일반적으로 알려져 있는 얕은 복사를 나타낸다.
이 말인 즉슨 원본이나 복사본을 변경하면, 다른 객체 또한 변경될 수 있다는 의미이다.
나는 공부를 할때 실생활에 비유를 들어서 나만의 언어대로 재정의를 내릴때 더 잘 이해가 되는 느낌이라
얕은 복사를 실생활에 비유를 해보겠다. ( gpt 가 많은 도움을 주었다. Thanks to GPT..)
" 공유 냉장고 " 라고 생각해보자.
어떤 집 (객체) 가 있고 그 집에 냉장고 (원본 객체) 가 있다고 생각해보자.
그 냉장고 안에는 우유 (속성 값) 이 들어있다.
그러던 어느날, 친구가 와서 "나도 똑같은 냉장고를 가질래!" 라고 하면서 새로운 냉장고(복사본 객체)를 가져갔다.
그런데 이친구는 알고보니 냉장고를 새로 산 게 아니라 원래 냉장고 안의 우유를 같이 쓰기로 한 것이다.
즉, 우유 자체 (속성 값) 은 그대로고, 두 개의 냉장고가 같은 우유를 가리키고 있는 상태이다.
여기서 만약 친구가 그 우유를 마시면??
👉 원래 냉장고에도 우유가 사라짐!
반대로 내가 원래 냉장고에서 우유를 버리면?
👉 친구 냉장고에서도 우유가 없어짐!
이게 바로 얕은 복사 ( Shallow Copy ) 라고 할 수 있을 것 같다.
=> 객체의 속성 (우유) 자체를 복사하는 것이 아니라, 같은 속성을 가르키는 참조만 복사한 것이다.
위 비유를 이용해 얕은 복사의 예제를 한번 확인해보자
const original = { milk: "🥛" };
const copy = { ...original }; // 얕은 복사
copy.milk = "🧃"; // 복사본에서 우유를 주스로 변경
console.log(original.milk); // 🥛 (변화 없음)
console.log(copy.milk); // 🧃 (바뀜)
아니 얕은 복사는 복사하고 바꾸면 원본이랑 같이 바뀐다는데 위 예제에서는 왜 원본은 변화가 없지 싶을 것이다.
그 이유는 얕은 복사가 객체의 1단계 속성만 복사하고 만약 속성이 참조 타입 (Object, Array 등) 인 경우 원본과 복사본이 같은 메모리 주소를 참조하게 된다.
하지만 원시 타입(string, number, boolean 등)인 경우, 값 자체가 복사되어 원본과 복사본이 독립적으로 변경될 수 있다.
다시 위 예제로 돌아가보자
const original = { milk: "🥛" }; // milk: "🥛" 의 경우 원시 타입(string) 이므로 값 자체가 복사 즉 원본과 복사본이 독립적
const copy = { ...original }; // original의 속성(프로퍼티)들을 새로운 객체에 복사
copy.milk = "🧃"; // 복사본에서 우유를 주스로 변경
console.log(original.milk); // 🥛 (변화 없음)-> 원시 타입이기 때문에 새로운 메모리에 저장되었기 때문
console.log(copy.milk); // 🧃 (바뀜)
위는 얕은 복사가 맞지만, 원시 값을 복사했기 때문에 깊은 복사처럼 보이는 예제이다. 그러면 진짜 얕은 복사 효과를 볼 수 있는 예제는 아래와 같다.
const original = { fridge: { milk: "🥛" } }; // 내부 객체 포함, 참조타입
const copy = { ...original }; // 얕은 복사 -> 원본과 복사본이 같은 fridge 객체를 참조하고 있음.
// 때문에 하나를 바꾸면 둘 다 영향을 받게 됨.
copy.fridge.milk = "🧃"; // 복사본에서 우유를 변경
console.log(original.fridge.milk); // 🧃 (원본도 바뀜!)
console.log(copy.fridge.milk); // 🧃 (복사본도 바뀜)
아래의 표로 원시타입과 참조타입의 차이를 구분해보자
| 타입 | 복사 방식 | 원본과 복사본 관계 | 값 변경 시 영향 |
| 원시 타입(숫자, 문자열, boolean) | 값 자체 복사 | 독립적인 값 | 복사본을 변경해도 원본 영향 없음. |
| 참조 타입(object, array) | 주소 (참조) 복사 | 같은 메모리 주소 공유 | 복사본을 변경하면 원본도 같이 변경됨. |
그럼 Javascript에서 어떤 내장 메서드가 얕은 복사를 수행할까?
1. 전개 연산자( ... )
위 예제에서도 쓰인 " ... " 전개 연산자는 얕은 복사를 수행한다.
2. Object.assign()
위 메서드는 아래의 예제처럼 사용 될 수 있다.
const original = { name: "철수", info: { age: 28 } };
const copy = Object.assign({}, original); // 얕은 복사
copy.info.age = 30;
console.log(original.info.age); // 30 (원본도 변경됨)
console.log(copy.info.age); // 30 (복사본도 변경됨)
3. Array.prototype.slice() 배열 복사
const original = [[1, 2], [3, 4]];
const copy = original.slice(); // 얕은 복사
copy[0][0] = 99;
console.log(original[0][0]); // 99 (원본도 변경됨!)
console.log(copy[0][0]); // 99 (복사본도 변경됨)
배열 내부의 배열(중첩된 배열)은 참조를 공유하기 때문에, copy를 변경하면 original도 변경된다.
4. Array.from()
const original = [[1, 2], [3, 4]];
const copy = Array.from(original); // 얕은 복사
copy[0][0] = 99;
console.log(original[0][0]); // 99 (원본도 변경됨!)
console.log(copy[0][0]); // 99 (복사본도 변경됨)
위 메서드를 사용해 얕은 복사를 수행할 수 있지만 깊은 복사를 수행하기 위한 메서드는 어떻게 있을까?
그걸 알아보기 전에 깊은 복사의 정의에 대해서 먼저 짚고 넘어가보자.
객체의 깊은 복사는 복사본의 속성이 복사본이 만들어진 원본 객체와 같은 참조(메모리 내의 같은 값을 가리킴)를 공유하지 않는 복사
따라서 원본이나 복사본을 변경할 때, 다른 객체가 변경되지 않는 다는 것을 보장할 수 있다.
깊은복사를 비유로 설명하자면 " 각자 따로 냉장고를 가진 룸메이트 " 라고 할 수 있겠다.
친구가 자신만의 새로운 냉장고를 가져갔지만 내 냉장고에는 여전히 우유가 있고 친구 냉장고엔 주스를 넣었다고 했을때
이젠 친구가 주스를 마셔도 내 냉장고에는 영향을 주지 않는다.
=> 즉 완전히 독립된 객체라는 뜻이다.
const original = { fridge: { milk: "🥛" } };
const copy = JSON.parse(JSON.stringify(original)); // 깊은 복사
copy.fridge.milk = "🧃"; // 복사본을 바꿔도
console.log(original.fridge.milk); // 🥛 (원본은 그대로!)
얕은 복사를 할때 사용된 메서드가 달라졌다.!
깊은 복사를 할때에는 참조 값의 속성을 수정해도 복사본에는 전혀 영향을 끼치지 않는 결과를 확인 할 수 있다.
그럼 위의 얕은 복사처럼 JS에서 어떻게 깊은 복사를 수행할 수 있을까?
1. JSON 방식 ( JSON.parse(JSON.stringify(obj)) )
const original = { name: "철수", info: { age: 28 } };
const deepCopy = JSON.parse(JSON.stringify(original)); // 깊은 복사
deepCopy.info.age = 30;
console.log(original.info.age); // 28 (원본 유지됨!)
console.log(deepCopy.info.age); // 30 (복사본만 변경됨)
위 예제는 JSON.stringify() 를 사용해 객체를 JSON 문자열로 변환한 다음, JSON,parse()로 문자열을 다시 완전히 새롭게 Js 객체로 변환하는 것이다.
더 찾아보니 structuredClone() 이라는 Web API 가 있다.
이또한 깊은 복사본을 만들고 원본에서 전송 가능한 객체를 복사하는 대신 새로운 복사본으로 전송할수 있다는 장점이 있다.
또한 Error 와 같은 더 많은 데이터 타입을 처리한다.
하지만 해당 메서드는 js 자체의 기능이 아니라 Web API 즉 브라우저에서 제공하는 기능이기 때문에 Node.js 환경에서는 사용이 불가능하다. Chrome, Edge, Firefox 같은 브라우저에서는 사용할수 있지만 Node.js 에서는 실행하면 에러가 난다.
또한 structuredClone() 도 직렬화가 가능해야 작동한다.
"직렬화"란? 데이터를 문자열 처럼 변환하는 과정이다. 즉 저장하거나 네트워크로 전송할 수 있는 형태로 변환하는 것이다.
때문에 직렬화 할 수 없는 객체( 예 : 함수, BigInt, Dom 요소 등) 을 포함하면 오류가 발생한다..!
여기까지 얕은 복사와 깊은 복사에 대해서 공부한 내용을 정리해봤다.
위 개념은 쉬워보이지만 변수의 데이터 할당과정을 온전히 이해하지 못한다면 쉽사리 이해하기 어렵다고 생각한다..!
코어 자바스크립트 책을 보면서 변수 할당 과정에 대해서 이해할 수 있었기 때문에 공부할 때 아하! 모먼트들이 종종 있었던 것 같다.
참고
http://developer.mozilla.org/ko/
MDN Web Docs
The MDN Web Docs site provides information about Open Web technologies including HTML, CSS, and APIs for both Web sites and progressive web apps.
developer.mozilla.org
https://product.kyobobook.co.kr/detail/S000001766397
코어 자바스크립트 | 정재남 - 교보문고
코어 자바스크립트 | 자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다!최근 웹 개발 진영은 빠르게 발전하고 있으며, 그 중심에는 자바스크립트가 있다고
product.kyobobook.co.kr
'공부 > Javascript' 카테고리의 다른 글
| [JS] Execution context & Scope ( 실행 컨텍스트 & 스코프 ) (0) | 2025.10.15 |
|---|