JavaScript – 복사했는데 왜 같이 바뀔까?: 얕은 복사와 깊은 복사

hogu__giriboy·2026년 3월 16일

위클리

목록 보기
7/11

1. 그대로 복사했지만, 바뀌는 건 원본

JavaScript를 공부하다 보면 한 번쯤 이런 상황을 만나게 된다.

const user = { name: "Alice" };

const copy = user;

copy.name = "Bob";

console.log(user.name); //Bob

코드를 보면 usercopy
복사한 것처럼 보이지만 실제로는 같은 객체를 참조하게 된다.

그래서 많은 사람들이 이렇게 생각한다.

copy를 수정했으니까 copy만 바뀌겠지?

하지만 실제 결과는 그렇지 않다.

copy.name"Bob"으로 바꾸면
같은 객체를 참조하고 있기 때문에 원본인 user.name"Bob"으로 바뀐다.

즉 이런 상황이 된다.

  • user → name: Bob
  • copy → name: Bob

여기서 자연스럽게 이런 질문이 생긴다.

왜 복사했는데 같이 바뀔까?

JavaScript에서 복사는 어떻게 동작하는 걸까?

이 질문의 답을 이해하려면
먼저 JavaScript에서 데이터가 어떻게 저장되는지를 알아야 한다.

특히 중요한 포인트는 하나다.

JavaScript에서 객체 변수에는 객체 자체가 저장되는 것이 아니라
객체를 가리키는 참조 값이 저장된다.

이 개념을 이해하면
왜 이런 일이 발생하는지 바로 보이기 시작한다.


2. JavaScript의 객체 저장법

앞에서 봤던 예제를 다시 보면 이런 코드였다.

const user = { name: "Alice" };
const copy = user;

copy.name = "Bob";

겉으로 보면 usercopy복사된 것처럼 보인다.
하지만 JavaScript 내부에서는 실제로 복사가 일어나지 않는다.

왜냐하면 객체와 배열은 변수에 객체 자체가 저장되는 것이 아니라
객체를 가리키는 참조 값이 저장되기 때문이다.

원시 타입은 값이 복사된다

먼저 원시 타입을 보자.

let a = 10;
let b = a;

b = 20;

console.log(a); //10

이 경우에는 a값 자체가 복사된다.

즉 메모리에서는 이렇게 된다.

  • a → 10
  • b → 10

그래서 b를 바꿔도 a는 영향을 받지 않는다.

객체는 참조가 복사된다

하지만 객체나 배열은 다르게 동작한다.

const user = { name: "Alice" };
const copy = user;

이때 실제 구조는 이런 느낌이다.

user ─┐
      ├──> { name: "Alice" }
copy ─┘

usercopy같은 객체를 가리키고 있는 상태다.

그래서 copy를 수정하면

copy.name = "Bob";

실제로는 같은 객체를 수정하는 것이 된다.

user ─┐
      ├──> { name: "Bob" }
copy ─┘

여기서 중요한 포인트는
객체나 배열에서 =로 대입하면
객체 자체가 복사되는 것이 아니라 같은 객체를 가리키는 참조 값이 복사된다.

그래서 개발에서는
객체를 "진짜로 복사"하는 방법이 필요해진다.


3. 겉만 복사되는 얕은 복사

앞에서 봤듯이 객체를 단순히 대입하면

const copy = user;

이건 복사가 아니라 같은 객체를 가리키는 것이었다.

그래서 개발에서는 객체를 새로 만들어 값을 복사하는 방법을 사용한다.
대표적인 방법이 바로 Spread 문법(...)이다.

const user = { name: "Alice" };

const copy = { ...user };

copy.name = "Bob";

console.log(user.name); // Alice

이번에는 copy를 수정해도
원본인 user는 바뀌지 않는다.

즉 이번에는 정말로 복사가 된 것처럼 보인다.

하지만 여기서 중요한 포인트가 있다.

Spread로 복사한 것은 완전한 복사가 아니라 "얕은 복사"다.

중첩 객체에서 발생하는 문제

객체 안에 객체가 들어 있는 경우를 보자.

const user = {
  name: "Alice",
  address: {
    city: "Seoul",
  },
};

const copy = { ...user };

copy.address.city = "Busan";

console.log(user.address.city); // Busan

이번에는 다시 원본이 같이 바뀌어 버린다.

왜 이런 일이 생길까?

Spread는 객체의 최상위 프로퍼티만 복사하기 때문이다.

즉 구조는 이렇게 된다.

user
 ├ name → "Alice"
 └ address ─┐
            └ { city: "Seoul" }

copy
 ├ name → "Alice"
 └ address ─┘ (같은 객체)

name 같은 원시 값은 새로운 값으로 복사되지만
address 같은 객체는 참조 값이 복사된다.

그래서 copy.address.city를 바꾸면
같은 객체를 보고 있는 user도 같이 바뀐다.

이처럼 객체의 최상위 프로퍼티만 복사되고 내부 객체는 참조가 공유되는 방식
얕은 복사 (Shallow Copy)라고 한다.


4. 완전히 분리되는 깊은 복사

앞에서 본 얕은 복사는 객체의 최상위 프로퍼티만 복사된다.
그래서 내부에 객체가 있으면 여전히 같은 데이터를 공유하게 된다.

이 문제를 해결하려면 내부 객체까지 전부 새로 복사해야 한다.
이것을 깊은 복사(Deep Copy)라고 한다.

깊은 복사는 객체 내부에 있는 모든 중첩 객체까지
재귀적으로 복사하여 완전히 새로운 객체 구조를 만드는 것이다.

JSON을 이용한 깊은 복사

가장 흔히 사용되는 방법 중 하나는 JSON을 이용하는 방식이다.

const user = {
  name: "Alice",
  address: {
    city: "Seoul",
  },
};

const copy = JSON.parse(JSON.stringify(user));

copy.address.city = "Busan";

console.log(user.address.city); // Seoul

이 방식은 객체를 JSON 문자열로 변환한 뒤 다시 객체로 변환한다.
이 과정에서 직렬화 가능한 데이터만을 기준으로 새로운 객체가 생성된다.

그래서 이제 구조는 이렇게 된다.

user ──> { name: "Alice", address: { city: "Seoul" } }

copy ──> { name: "Alice", address: { city: "Seoul" } }

두 객체가 완전히 분리된 상태다.

그래서 copy를 수정해도
user에는 영향을 주지 않는다.

structuredClone

최근에는 JavaScript에서 깊은 복사를 위한 내장 함수도 제공한다.

const copy = structuredClone(user);

이 함수는 객체 구조를 깊게 복사하도록 설계된 API다.

그래서 JSON 방식보다 더 다양한 데이터 타입을 지원하며
깊은 복사를 수행할 수 있다.

여기까지 정리해봤을 때 흐름이 이렇게 된다.

  • 단순 대입 → 참조 공유
  • Spread → 얕은 복사
  • structuredClone/JSON 방식 → 깊은 복사

5. 실제 개발에서 복사의 중요성

얕은 복사와 깊은 복사는 단순히 문법 문제가 아니라
데이터의 참조 관계와 변경 방식과 연결된 개념이다.

개발을 하다 보면 객체나 배열의 데이터를 변경해야 하는 상황이 자주 생긴다.

예를 들어 어떤 사용자 데이터를 다룬다고 해보자.

const user = {
  name: "Alice",
  age: 25,
};

이 데이터를 수정하면서도 원본 데이터를 유지해야 하는 상황이 생길 수 있다.

그래서 보통은 기존 데이터를 복사한 뒤 후 복사본을 수정한다.

const updatedUser = { ...user };

updatedUser.age = 26;

이렇게 하면 원본 데이터는 그대로 두고
새로운 데이터를 만들어 사용할 수 있다.

이 방식은 특히 다음과 같은 상황에서 중요해진다.

상태 관리

프론트엔드에서는 상태(state)를 관리할 때
기존 데이터를 직접 수정하지 않는 방식을 많이 사용한다.

대신 기존 데이터를 복사하고 필요한 부분만 수정한 뒤 새로운 상태로 교체한다.

이때 얕은 복사/깊은 복사 개념을 이해하지 못하면
의도하지 않게 원본 데이터까지 바뀌는 버그가 생길 수 있다.

데이터 수정 과정

객체 안에 객체가 있는 구조에서는
얕은 복사로 인해 중첩 데이터가 함께 변경되는 문제가 자주 발생한다.

그래서 데이터를 다룰 때는 얕은 복사로 충분한지,
내부 객체까지 복사하는 깊은 복사가 필요한지 상황에 맞게 판단해야 한다.


6. 핵심 정리

  • JavaScript에서 객체와 배열 변수에는 객체 자체가 아니라 객체를 가리키는 참조 값이 저장된다.
  • 객체나 배열에서 =로 대입하면 객체 자체가 복사되는 것이 아니라 같은 객체를 가리키는 참조 값이 복사된다.
  • Spread 문법(...)은 얕은 복사로, 객체의 최상위 프로퍼티만 복사된다.
  • 객체 안에 객체가 있는 경우 내부 객체는 여전히 같은 참조를 공유한다.
  • 내부 데이터까지 완전히 분리하려면 깊은 복사가 필요하다.
  • JSON.parse(JSON.stringify())structuredClone() 같은 방법을 사용하면 중첩 객체까지 새로 복사할 수 있다.
  • 데이터를 수정할 때 원본을 유지해야 하는 상황에서는 복사 방식에 대한 이해가 중요하다.

0개의 댓글