얕은 복사와 깊은 복사 — 겉만 새로 만드는가, 속까지 새로 만드는가

스프레드로 복사했는데 원본이 같이 바뀌는 이유. 복사가 최상위 상자만 새로 만드는지, 안에 든 객체까지 새로 만드는지의 차이다.

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 의 주소가 들어 있다.

인터랙티브 다이어그램: 얕은 복사는 바깥 상자만 새로 만든다 (단계별로 재생할 수 있다)

  1. 원본 상자 A 에는 name 값과 B 의 주소가 들어 있다.
  2. { ...user } 는 새 상자 A' 를 만들고 칸을 하나씩 옮겨 적는다. name 은 값이 복사된다.
  3. address 칸에는 B 의 주소가 적혀 있으니 주소가 그대로 옮겨진다. B 는 새로 생기지 않는다.
  4. copy.address.city = "부산" 은 B 를 고친다. user 도 B 를 보고 있으니 같이 바뀐다.
  5. 처음부터 깊은 복사(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 다.
바깥은 새것, 안쪽은 같은 것 — 얕은 복사를 한 줄로 보여준다.


흔한 실수


한 줄 정리

얕은 복사는 맨 바깥 상자만 새로 만들고 안쪽 객체는 주소를 옮겨 적어 공유한다.
깊은 복사는 안쪽 상자까지 전부 새로 만든다. 중첩이 없으면 둘은 같다.

참고

  1. [1] Shallow copy — MDN Web Docs 용어 사전
  2. [2] Deep copy — MDN Web Docs 용어 사전
  3. [3] structuredClone() — MDN Web Docs

관련 글