🗿 Immutability
1️⃣ 불변성이란?
불변성(Immutability) 은 "값을 변경하지 않는다"는 개념이 아닙니다.
정확히는 기존 값으 수정하는 대신, 변경된 값은 담는 새로운 데이터를 만든다라는 의미입니다.
- ❌ “데이터를 바꾼다”
- ✅ “바뀐 내용을 가진 새 데이터를 만든다”
이 방식은 코드의 예측 가능성을 높이고, 버그를 줄이며, 상태 변화의 추적을 용이하게 합니다.
2️⃣ 변수 이름의 불변성 : const vs var
- 변수명(identifier)은 메모리 주소를 가리키느 '라벨'
- const는 "값이 불변"이 아니라 라벨이 가리키는 주소가 불변이라는 의미
🧐 예시
var x = 10;
x = 20; // O, 재할당 가능
const y = 10;
y = 20; // ❌ TypeError: Assignment to constant variable.
🧭 메모리 도식
# var
<a id="var"></a>
x ─> ┌────┐
│ 10 │
└────┘
x 재할당
x ─> ┌────┐
│ 20 │
└────┘
# const
<a id="const"></a>
y ─> ┌────┐
│ 10 │
└────┘
y = 20 (불가능)
2️⃣ 변수 할당 방식: Primitive vs Reference
JavaSript의 값은 두 가지 방식으로 저장됩니다.
| 구분 | 저장 방식 | 예시 |
|---|---|---|
| Primitive | 값 자체 저장 | number, string, boolean... |
| Reference | 메모리 주소 저장 | object, array, function |
🧐 Primitive 예시
let a = 10;
let b = a;
a = 20;
🧭 메모리 도식
a ─┐
▼
┌────┐
│ 10 │
└────┘
▲
b ─┘
a = 20 이후
a ─> ┌────┐
│ 20 │
└────┘
b ─> ┌────┐
│ 10 │
└────┘
3️⃣ 초기값 비교: === 의 동작 원리
🔹 Primitive 비교는 “값 비교”
10 === 10; // true
"abc" === "abc" // true
🔹 Reference 비교는 “주소 비교”
{} === {} // false (다른 주소)
[] === [] // false
// obj1 ─> [0x01]
// obj2 ─> [0x02]
// obj1 !== obj2
4️⃣ 객체의 가변성 (Mutability)
객체는 주소를 공유하기 때문에 변경하면 모든 참조에서 값이 바뀝니다.
🧐 예시
const a = { value: 10 };
const b = a;
b.value = 20;
console.log(a.value); // 20
🧭 메모리 도식
a ┐
▼
┌──────────────┐
│ { value:10 } │
└──────────────┘
▲
b ┘
# b.value에 20 할당 이후
<a id="bvalue에-20-할당-이후"></a>
a ┐
▼
┌──────────────┐
│ { value:20 } │
└──────────────┘
▲
b ┘
5️⃣ 객체의 복사 (Shallow Copy)
최상위 값만 새 객체에 복사하고 내부 중첩 객체는 같은 주소를 공유합니다.
🧐 예시
const user = {
name: "Lee",
pet: { type: "cat" }
};
const copy = { ...user };
copy.pet.type = "dog";
console.log(user.pet.type); // "dog"
🧭 메모리 도식
user ───────────┐
▼
┌──────────────────────┐
│ name: "Lee" │
│ pet ─────────────┐ │
└──────────────────────┘
│
│ 동일한 pet 주소
▼
┌──────────────────────────┐
│ { type: "cat" -> "dog" } │
└──────────────────────────┘
▲
│ 동일한 pet 주소
┌──────────────────────┐
│ name: "Lee" │
│ pet ─────────────┘ │
└──────────────────────┘
▲
copy ───────────┘
- 얕은 복사는 겉 객체만 새로 만들고, 내부에 포함된 객체(
pet)는 같은 메모리 주소를 공유 - 따라서,
copy.pet을 수정하면,user.pet도 같은 객체를 가리키고 있기 때문에 함께 변경
6️⃣ 중첩 객체 복사 (Deep Copy)
🧐 예제
const user = {
name: "Lee",
pet: { type: "cat" }
};
const deep = structuredClone(user);
deep.pet.type = "dog";
console.log(user.pet.type); // "cat"
🧭 메모리 도식
# 원본 user 객체
<a id="원본-user-객체"></a>
user ────────┐
▼
┌───────────────────────┐
│ name: "Lee" │
│ pet ───────────────┐ │
└───────────────────────┘
▼
┌─────────────────┐
│ { type: "cat" } │ ← 메모리 주소 0x01
└─────────────────┘
# 깊은 복사본 deep
<a id="깊은-복사본-deep"></a>
deep ────────┐
▼
┌───────────────────────┐
│ name: "Lee" │
│ pet ───────────────┐ │
└───────────────────────┘
▼
┌─────────────────┐
│ { type: "dog" } │ ← 메모리 주소 0x02
└─────────────────┘
7️⃣ 불변 함수 만들기 (Pure Functions)
입력값을 변경하지 않고, 새로운 값을 만들어 반환하는 함수입니다.
🧐 예시 (❌ 가변)
function addAge(user) {
user.age++;
return user;
}
🧐 예시 (❌ 가변)
function addAge(user) {
return { ...user, age: user.age + 1 };
}
8️⃣ 가변 vs 불변 API 비교
| 가변(Mutable) | 불변(Immutable) |
|---|---|
| push | concat |
| pop | slice |
| shift | slice |
| splice | filter, map |
| sort | slice().sort() |
🧐 예시
const arr = [1,2,3];
const newArr = [...arr, 4]; // push 대신
9️⃣ Object.freeze
객체의 속성을 변경할 수 없도록 동결시킵니다.
const person = Object.freeze({ name: "Tom" });
person.name = "Jerry"; // 무시됨
- 하지만 얕은 freeze → 내부 객체는 여전히 mutable
🔟 const vs Object.freeze 차이
| 특징 | const | Object.freeze |
|---|---|---|
| 변수가 가리키는 주소 고정 | ✔ | ✖ (관계 없음) |
| 객체 내부 값 변경 가능? | ✔ 가능 | ✖ 불가능 |
| 불변성 보장? | 부분적 | 강력 |
🧐 예시
const obj = { a: 1 };
obj.a = 2; // OK
const frozen = Object.freeze({ a: 1 });
frozen.a = 2; // 무시됨
🎯 최종 요약
- primitive는 값 기반, 재할당 시 새 메모리
- 객체는 주소 기반, 변경 시 모든 참조가 영향
- 얕은 복사는 내부 객체 공유
- 깊은 복사는 완전 독립
- 불변성을 지키면 코드 예측 가능성 증가
- React/Redux 등 상태 관리에서 필수