자바스크립트에서 불변성(Immutable)에 대한 설명을 찾아보면 오히려 헷갈리는 경우가 많았다. 나도 처음에는 "불변성이 새로운 값을 할당할 때, 기존 값이 변경되는 것이 아니라, 새로운 메모리 공간에 저장된다"라고 단순하게 이해했었다. 하지만 이렇게만 알면 디테일이 빠져 공부할수록 더 혼란스러웠다.
따라서 이번 포스팅에서는 내가 직접 정리한 불변성과 가변성에 대한 개념을 명확하게 설명하고자 한다.
📌 불변성
자바스크립트에서 불변성을 구분하는 기준은
데이터가 변경될 때 "전체가 새로운 메모리에 할당되는가?" 또는 "일부만 새로운 메모리에 할당되는가?"이다.
✔ 데이터 전체가 새로운 메모리에 할당되면 불변, 일부만 변경되면 가변
기본형 데이터는 값이 변경될 때마다 새로운 메모리를 할당하므로 불변이다.
참조형 데이터는 값이 변경될 때 기존 객체를 유지하면서 내부 값만 새로운 메모리에 할당하므로 가변이다.
(참고로 자바스크립트의 메모리 구조는 크게 "변수 영역"과 "데이터 영역"으로 나뉜다.)
📌 기본형 데이터 (Primitive Type)
📍 기본형 데이터(string
, number
등)는 불변(Immutable) 하며, 값을 변경하면 데이터 영역의 기존 데이터는 그대로 유지되고, 전체를 새로운 메모리에 할당함.
let str; // 변수 str 선언
str = "Hello"; // 변수 str에 데이터 할당
str = "hello"; // 새로운 문자열 할당
console.log(str); // "hello"
🛠 메모리 변화 과정
1️⃣ let str;
(변수 선언)
📌 변수 영역:
┌────1003───┐
│ str: │
└───────────┘
- 메모리에서 비어있는 공간을 확보하고 그 공간의 이름을 설정하는 선언 과정
2️⃣ str = "Hello";
(값 할당)
📌 변수 영역: 📌 데이터 영역:
┌────1003────┐ ┌─────5001─────┐
│ str: @5001 │ → │ "Hello" │
└────────────┘ └──────────────┘
"Hello"
라는 문자열이 데이터 영역에 저장되고,str
은 그 주소(5001
)를 참조.
3️⃣ str = "hello";
(새로운 값 할당)
📌 변수 영역: 📌 데이터 영역:
┌────1003────┐ ┌─────5001─────┐
│ str: @6001 │ → │ "Hello" │ (GC 대상)
└────────────┘ └──────────────┘
┌─────6001─────┐
│ "hello" │
└──────────────┘
새로운 문자열
"hello"
가 생성되고, 변수str
는 새로운 메모리 주소(@6001)를 참조."Hello"
는 더 이상 사용되지 않으면 GC(Garbage Collector)의 대상이 됨.
→ 즉 기존 값을 수정하는 것이 아니라, 전체 데이터를 새로운 메모리에 저장하고 변수가 이를 가리키도록 한다.
📌 참조형 데이터 (Reference Type)
📍 참조형 데이터(object
, array
, function
등)가 가변적이라고 말하는 것은, 내부 프로퍼티를 변경할 때를 말한다. 내부 속성(프로퍼티)을 변경할 때, 전체 객체가 새로 할당되는 것이 아니라 일부만 새로운 메모리에 할당된다.
기본형 데이터와의 차이는 ‘객체의 변수(프로퍼티) 영역’이 별도로 존재한다는 점이다.
✅ 내부 속성(프로퍼티) 변경 → 가변성
let obj1 = { name: "Alice", age: 25 };
let obj2 = obj1;
obj2.name = "Bob";
🛠 메모리 변화 과정
1️⃣ let obj1 = { name: "Alice", age: 25 };
(객체 생성)
📌 변수 영역: 📌 데이터 영역: 📌 객체의 변수(프로퍼티) 영역:
┌─────1003─────┐ ┌─────────5001─────────┐ ┌───────7001───────┐
│ obj1: @5001 │ → │ @7001, @7002 │ → │ name: @6001 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────────6001─────────┐ ┌───────7002───────┐
│ "Alice" │ │ age: @6002 │
└──────────────────────┘ └──────────────────┘
┌─────────6002─────────┐
│ 25 │
└──────────────────────┘
변수
obj1
은 객체(@5001
)의 주소를 참조하고 있다.객체 내부 프로퍼티
name
과age
는 각각@6001
,@6002
를 참조하여 저장됨.
2️⃣ obj2 = obj1;
(새로운 변수에 참조값 할당)
📌 변수 영역: 📌 데이터 영역: 📌 객체의 변수(프로퍼티) 영역:
┌─────1003─────┐ ┌─────────5001─────────┐ ┌───────7001───────┐
│ obj1: @5001 │ → │ @7001, @7002 │ → │ name: @6001 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────1002─────┐ ┌─────────6001─────────┐ ┌───────7002───────┐
│ obj2: @5001 │ │ "Alice" │ │ age: @6002 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────────6002─────────┐
│ 25 │
└──────────────────────┘
obj2
는obj1
과 동일한 객체(@5001
)를 참조한다.즉,
obj1
과obj2
는 같은 메모리 주소를 가리키고 있음.
3️⃣ obj2.name = "Bob";
(객체 속성 변경)
📌 변수 영역: 📌 데이터 영역: 📌 객체의 변수(프로퍼티) 영역:
┌─────1003─────┐ ┌─────────5001─────────┐ ┌───────7001───────┐
│ obj1: @5001 │ → │ @7001, @7002 │ → │ name: @6003 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────1002─────┐ ┌─────────6001─────────┐ ┌───────7002───────┐
│ obj2: @5001 │ │ "Alice" │(GC) │ age: @6002 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────────6002─────────┐
│ 25 │
└──────────────────────┘
┌─────────6003─────────┐
│ "Bob" │
└──────────────────────┘
객체의 참조값(
@5001
)은 그대로 유지됨name
프로퍼티의 값만 새로운 메모리(@6003)
로 변경됨
즉, 내부 프로퍼티만 변경되며, 기존 객체는 유지되므로 가변(Mutable)하다.
📌 불변 객체
위에서 보다시피 참조형 데이터가 가변적이다라고 말하는 것은, 내부 프로퍼티를 변경할 때를 말한다.
즉 데이터 자체를 변경하려고 하면(새로운 전체 데이터를 할당하고자하면) 기본형과 마찬가지로 기존 데이터는 변하지 않습니다.
✅ 객체 자체를 새로운 값으로 할당 → 불변성
const obj1 = { name: "Alice", age: 25 };
const obj2 = obj1; // 같은 객체를 참조
obj2 = { name: "Bob", age: 30 }; // 새로운 객체 할당 (재할당)
console.log(obj1.name); // "Alice" (원본 유지)
console.log(obj2.name); // "Bob" (새로운 객체)
🛠 메모리 변화 과정
📌 변수 영역: 📌 데이터 영역: 📌 객체의 변수(프로퍼티) 영역:
┌─────1003─────┐ ┌─────────5001─────────┐ ┌───────7001───────┐
│ obj1: @5001 │ → │ @7001,@7002 │ → │ name: @6001 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────────6001─────────┐ ┌───────7002───────┐
│ "Alice" │ │ age: @6002 │
└──────────────────────┘ └──────────────────┘
┌─────────6002─────────┐
│ 25 │
└──────────────────────┘
✅ 2️⃣ const obj2 = obj1;
(같은 객체를 참조)
📌 변수 영역: 📌 데이터 영역: 📌 객체의 변수(프로퍼티) 영역:
┌─────1003─────┐ ┌─────────5001─────────┐ ┌───────7001───────┐
│ obj1: @5001 │ → │ @7001,@7002 │ → │ name: @6001 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────1002─────┐ ┌─────────6001─────────┐ ┌───────7002───────┐
│ obj2: @5001 │ │ "Alice" │ │ age: @6002 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────────6002─────────┐
│ 25 │
└──────────────────────┘
✅ 3️⃣ obj2 = { name: "Bob", age: 30 };
(새로운 객체 할당)
📌 변수 영역: 📌 데이터 영역: 📌 객체의 변수(프로퍼티) 영역:
┌─────1003─────┐ ┌─────────5001─────────┐ ┌───────7001───────┐
│ obj1: @5001 │ → │ @7001,@7002 │ → │ name: @6001 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────1002─────┐ ┌─────────5002─────────┐ ┌───────7002───────┐
│ obj2: @5002 │ → │ @8001,@8002 │ │ age: @6002 │
└──────────────┘ └──────────────────────┘ └──────────────────┘
┌─────────6001─────────┐ ┌───────8001───────┐
│ "Alice" │ │ name: @6003 │
└──────────────────────┘ └──────────────────┘
┌─────────6002─────────┐ ┌───────8002───────┐
│ 25 │ │ age: @6004 │
└──────────────────────┘ └──────────────────┘
┌─────────6003─────────┐
│ "Bob" │
└──────────────────────┘
┌─────────6004─────────┐
│ 30 │
└──────────────────────┘
obj2
는 새로운 객체(@5002
)를 참조하면서,obj1
과 연결이 끊어졌다.
📌 정리
✔ 기본형 데이터는 전체 데이터를 새로운 메모리에 저장하므로 불변(Immutable)하다.
✔ 참조형 데이터는 내부 프로퍼티만 변경되며, 기존 객체는 유지되므로 가변(Mutable)하다.
✔ 객체 자체를 새로운 값으로 할당하면 새로운 메모리가 생성되므로 불변성처럼 동작한다.
📌 그렇다면 왜 불변 객체가 중요한가?
✅ 데이터의 예측 가능성을 유지하기 위해
- 객체가 가변적이면, 하나의 변수가 변경될 때 같은 객체를 참조하는 다른 변수들도 영향을 받는다.
✅ 상태 관리에서 필수
리액트와 같은 UI 프레임워크에서는 상태(state)가 변경될 때 UI가 리렌더링됨.
만약 객체를 직접 변경하면 변경 감지가 어려워지고, UI가 예상대로 동작하지 않을 수 있음.
즉 불변성과 가변성의 개념을 정확히 이해하면, 상태 관리와 성능 최적화에서 큰 도움이 된다! 🔥
그렇다면 불변 객체를 만들려면 어떻게 해야할까?
다음 포스팅을 기대해주세요🚀