얕은 복사와 깊은 복사 — 겉만 새로 만드는가, 속까지 새로 만드는가
스프레드로 복사했는데 원본이 같이 바뀌는 이유. 복사가 최상위 상자만 새로 만드는지, 안에 든 객체까지 새로 만드는지의 차이다.
Seobway · · 7분
복사했는데 왜 원본이 바뀌나
스프레드(...)로 복사했다. 복사본만 고쳤다. 그런데 원본도 바뀌어 있다.
const user = { name: "민수", address: { city: "서울" } };
const copy = { ...user };
copy.name = "영희"; // 원본 그대로 — 여기까진 괜찮다
copy.address.city = "부산"; // 원본도 부산이 된다 ← ?
user.name; // "민수"
user.address.city; // "부산"
name 은 따로 놀고 address 는 같이 논다. 같은 복사인데 결과가 반반이다.
이 반반이 얕은 복사다.
어떻게 동작하는가
먼저 참조 타입에서 본 것 하나만 기억하면 된다.
객체를 담은 변수에는 객체가 아니라 주소가 들어 있다.
그러면 user 는 이렇게 생겼다.
user ──▶ { name: "민수", address: ──▶ { city: "서울" } }
─────── 상자 A ─────── ─── 상자 B ───
상자가 두 개다. 바깥 상자 A 안에 address 라는 칸이 있고, 그 칸에는
상자 B 의 주소가 들어 있다.
인터랙티브 다이어그램: 얕은 복사는 바깥 상자만 새로 만든다 (단계별로 재생할 수 있다)
- 원본 상자 A 에는 name 값과 B 의 주소가 들어 있다.
- { ...user } 는 새 상자 A' 를 만들고 칸을 하나씩 옮겨 적는다. name 은 값이 복사된다.
- address 칸에는 B 의 주소가 적혀 있으니 주소가 그대로 옮겨진다. B 는 새로 생기지 않는다.
- copy.address.city = "부산" 은 B 를 고친다. user 도 B 를 보고 있으니 같이 바뀐다.
- 처음부터 깊은 복사(structuredClone)였다면 B 도 새로 만들어 A' 는 B' 를 가리킨다.
정리하면 "몇 층까지 새로 만드느냐" 의 차이다.
| 새로 만드는 범위 | 대표 방법 | |
|---|---|---|
| 얕은 복사 | 맨 바깥 한 층 | { ...obj }, [...arr], Object.assign, arr.slice(), Array.from |
| 깊은 복사 | 안쪽 끝까지 전부 | structuredClone(obj) |
원시 값("민수", 3)은 원래 값 자체가 복사되니 층이 없다.
층이 생기는 건 객체 안에 객체가 있을 때뿐이다. 중첩이 없으면 얕은 복사로 충분하다.
직접 확인
=== 는 두 객체가 같은 상자인지 묻는다. 층마다 물어보면 어디까지 새로 만들어졌는지 보인다.
const user = {
name: "민수",
address: { city: "서울" },
tags: ["a", "b"],
};
const shallow = { ...user };
const deep = structuredClone(user);
console.log("바깥 상자가 같은가");
console.log(" shallow:", shallow === user); // false
console.log(" deep :", deep === user); // false
console.log("안쪽 address 가 같은가");
console.log(" shallow:", shallow.address === user.address); // true ← 공유
console.log(" deep :", deep.address === user.address); // false
shallow.address.city = "부산";
shallow.tags.push("c");
console.log("얕은 복사를 고친 뒤 원본:", user.address.city, user.tags);
deep.address.city = "대구";
console.log("깊은 복사를 고친 뒤 원본:", user.address.city); // 대구가 아니다
shallow === user 는 false 인데 shallow.address === user.address 는 true 다.
바깥은 새것, 안쪽은 같은 것 — 얕은 복사를 한 줄로 보여준다.
흔한 실수
한 줄 정리
얕은 복사는 맨 바깥 상자만 새로 만들고 안쪽 객체는 주소를 옮겨 적어 공유한다.
깊은 복사는 안쪽 상자까지 전부 새로 만든다. 중첩이 없으면 둘은 같다.
-
{ ...obj }로 복사한 뒤 중첩된 값을 고치면 왜 원본이 바뀌는지 설명할 수 있다 -
shallow.address === user.address가true인 이유를 안다 - 중첩 상태를 바꿀 때 어느 층을 새로 만들어야 하는지 안다
-
JSON복사 대신structuredClone을 쓰는 이유와 그 한계를 말할 수 있다
참고
- [1] Shallow copy — MDN Web Docs 용어 사전
- [2] Deep copy — MDN Web Docs 용어 사전
- [3] structuredClone() — MDN Web Docs
관련 글
- 참조 타입 — 주소가 복사되는 것들 → — 이 글의 선행
- 원시 타입 — 값 자체가 복사되는 것들 →
- 프로토타입 — 없으면 위에 물어보는 상속 → —
structuredClone이 끊는 연결
- javascript
- 얕은 복사
- 깊은 복사
- structuredClone
- 참조 타입