티스토리 뷰
00. TL;DR
- 자바스크립트의 값은 원시 값(Primitive Value) 과 참조 값(Reference Value) 으로 나뉨
- 이 둘은 메모리에 저장되는 방식, 비교 방식, 복사 방식에서 차이점이 존재함
01. 원시 값(Primitive Value)
01.01. 불변(Immutable)이란?
불변이란, 값을 한 번 생성하면 변경할 수 없는 성질을 의미합니다.
let a = 10;
a = 20; // 새로운 값으로 재할당한 것이지, 기존 값(10)을 변경한 것이 아님
값을 바꾸는 것이 아니라 새로운 값을 생성해서 변수에 할당하는 것입니다.
01.02. 메모리 구조: 스택과 값 자체 저장
원시 값은 스택(Stack)에 값 자체가 저장됩니다.
Stack:
a ──▶ 10
b ──▶ 10 (복사된 값)
여기서 a와 b는 각각 서로 다른 메모리 주소에 10이라는 값을 복사해서 저장하고 있습니다.
01.03. 왜 복사가 일어날까? (값에 의한 전달)
원시 값은 불변성(immutable)을 가지며, 변수에는 값 자체가 저장됩니다.
이러한 특성 때문에 원시 값을 다른 변수에 할당하거나 함수의 인자로 전달할 때는 값이 복사되어 전달됩니다.
이를 값에 의한 전달(Pass by Value)이라고 합니다.
let x = 5;
let y = x; // x의 값 5가 y에 복사됨
y = 10; // y를 변경해도 x는 변하지 않음
console.log(x); // 5 (변경 없음)
console.log(y); // 10
위 예제에서 x의 값이 y에 복사되었기 때문에, y를 변경해도 x에는 아무런 영향을 주지 않습니다.
즉, 원시 값은 항상 복사 기반으로 전달되며 독립적인 값을 유지합니다.
01.03.01. 왜 복사 방식으로 설계되었을까?
단순히 "참조로 전달하면 불변성이 깨지니까 안 돼"가 아니라, 다음과 같은 언어 설계 차원의 이유가 존재합니다.
- 작고 고정된 크기이기 때문에 복사가 효율적이다
대부분의 원시 값은 숫자, 불리언 등 고정된 크기의 데이터이며, 스택에 직접 저장되고 빠르게 복사할 수 있습니다. - 참조 공유는 코드의 예측 가능성을 낮춘다
만약 원시 값이 참조로 전달된다면, 한 쪽의 변경이 다른 변수에도 영향을 주게 됩니다.
→ 이런 동작은 버그의 원인이 되고, 테스트/디버깅이 매우 어려워집니다. - 참조를 쓸 필요가 없다
참조는 주로 객체처럼 복잡하고 용량이 큰 구조에서 메모리를 절약하기 위해 사용됩니다.
원시 값은 대부분 작고 단순하므로, 굳이 참조를 사용할 이유가 없습니다.
📌 따라서 원시 값은 복사를 통해 전달되도록 설계되었으며, 이는 성능, 안정성, 예측 가능성을 모두 고려한 언어 차원의 선택입니다.
01.04. 원시 값의 종류
원시 값은 모두 스택(Stack) 메모리에 직접 저장되며, 각 자료형은 크기나 표현 방식에 따라 고유한 방식으로 메모리에 할당됩니다.
또한, 원시 값은 불변이기 때문에 값이 바뀌면 새로운 메모리 공간이 할당됩니다.
01.04.01. Number
- 정수와 실수를 모두 표현하며, 64비트 부동소수점(IEEE 754) 방식으로 저장됩니다.
- 값 자체가 스택 메모리에 8바이트로 저장되며 복사 및 처리 속도가 빠릅니다.
[변수: score] ──▶ [메모리 주소: 0x1000] ──▶ [값: 95]
const score = 95;
⚠️ 정밀도 문제
console.log(0.1 + 0.2); // 0.30000000000000004
- 일부 실수는 이진수로 정확히 표현할 수 없어 계산 오차가 발생합니다.
해결 방법 예:
(0.1 + 0.2).toFixed(2); // "0.30"
Math.round((0.1 + 0.2) * 100) / 100; // 0.3
⚠️ 안전한 정수 범위
- 자바스크립트는 안전하게 표현할 수 있는 정수의 범위가 존재합니다.
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MIN_SAFE_INTEGER; // -9007199254740991
console.log(9007199254740991 + 1); // 9007199254740992
console.log(9007199254740991 + 2); // ⚠️ 여전히 9007199254740992 (정확하지 않음)
- 큰 정수를 다룰 땐
BigInt사용 권장
01.04.02. String
- 문자열은 유니코드 문자들의 시퀀스이며, 길이에 따라 동적으로 메모리 할당됩니다.
- 문자열은 불변이며, 수정 시 새로운 메모리 공간이 생성됩니다.
[변수: name] ──▶ [메모리 주소: 0x2000] ──▶ [값: "Odin"]
const name = "Odin";
01.04.03. Boolean
true또는false두 가지 상태만 가지며, 1비트 또는 1바이트로 표현됩니다.
[변수: isActive] ──▶ [메모리 주소: 0x3000] ──▶ [값: true]
const isActive = true;
01.04.04. null
- 내부적으로는 null도 특정 고정된 메모리 패턴으로 처리되며, 참조가 없음을 명시하는 특별한 값입니다.
[변수: value] ──▶ [메모리 주소: 0x4000] ──▶ [값: null]
const value = null;
01.04.05. undefined
undefined는 변수는 선언되었지만 값이 없음을 의미하며, 고정된 식별값으로 저장됩니다.
[변수: notDefined] ──▶ [메모리 주소: 0x5000] ──▶ [값: undefined]
let notDefined;
01.04.06. BigInt
BigInt는 정수 크기에 제한 없이 정수를 표현할 수 있는 자료형입니다.- 숫자가 매우 클 경우
Number대신 사용되며, 동적으로 메모리 할당됩니다.
[변수: big] ──▶ [메모리 주소: 0x6000] ──▶ [값: 123456789012345678901234567890n]
const big = 1234567890123456789012345678901234567890n;
01.04.07. Symbol
Symbol은 고유하고 변경 불가능한 식별자를 생성하는 자료형입니다.- 주로 객체의 프로퍼티 키로 사용되며, 메모리에는 설명과 고유 식별자가 함께 저장됩니다.
[변수: unique] ──▶ [메모리 주소: 0x7000] ──▶ [값: Symbol("id")]
const unique = Symbol("id");
01.05. 문자열은 왜 불변일까? (메모리 관점에서)
문자열은 문자 하나하나가 조각처럼 연결되어 저장되며, 수정 시 전체 문자열을 새로 생성합니다.
let greeting = "Hello";
greeting[0] = "h";
console.log(greeting); // Hello
01.05.01. 문자열은 어디에 저장될까?
대부분의 자바스크립트 엔진에서는 문자열은 힙(Heap) 에 저장됩니다.
길이가 가변적이고 불변 특성을 갖기 때문에, 고정 크기 스택보다 힙에 저장하는 것이 일반적입니다.
다만, 문자열 리터럴(String Literal)은 내부적으로 문자열 상수 풀이나 캐시 메모리처럼 관리되기도 합니다.
Stack:
str ──▶ 0x1001
Heap:
0x1001 ──▶ "Hello"
String Buffer란?
문자열이 불변이기 때문에, 문자열을 자주 수정하거나 연결할 경우 매번 새로운 문자열 객체가 생성됩니다.
이를 최적화하기 위해 자바스크립트 엔진은 내부적으로 문자열 결합 시 임시 버퍼처럼 문자열을 누적하여, 최종 결과만 한 번에 메모리에 할당합니다.
JavaScript 는 명시적인 StringBuffer는 없지만, 엔진이 문자열 결합 시 성능을 위해 유사 구조를 사용하고 있습니다.
// 문자열 결합 예 (자바스크립트)
let result = "";
for (let i = 0; i < 1000; i++) {
result += i; // 내부적으로 최적화
}
02. 참조 값(Reference Value)
02.01. 가변(Mutable)이란?
참조 값은 내부 상태를 자유롭게 변경할 수 있는 값입니다.
const obj = { name: "Odin" };
obj.name = "Soo";
console.log(obj.name); // Soo
02.02. 메모리 구조: 스택(참조) + 힙(값)
참조 값은 스택에 참조 주소가 저장되고, 힙(Heap)에 실제 값이 저장됩니다.
Stack:
obj1 ──▶ 0x001
obj2 ──▶ 0x001
Heap:
0x001 ──▶ { name: 'Soo' }
두 변수는 같은 힙 메모리 주소를 참조하고 있으므로, 둘 중 하나의 내부 속성을 변경하면 다른 쪽에서도 변경된 값을 확인할 수 있습니다.
02.03. 참조 값의 종류
02.03.01. Object
const user = { name: "Odin", age: 30 };
02.03.02. Array
const items = [1, 2, 3];
02.03.03. Function
function greet() {
console.log("Hello");
}
02.04. 값 복사의 두 방식
02.04.01. 얕은 복사(Shallow Copy)
한 단계까지만 복사하며, 내부 객체는 여전히 같은 참조를 가집니다.
const original = { name: "Soo", info: { age: 30 } };
const copy = { ...original };
copy.info.age = 40;
console.log(original.info.age); // 40
02.04.02. 깊은 복사(Deep Copy)
모든 하위 객체까지 재귀적으로 복사합니다.
const deepCopy = JSON.parse(JSON.stringify(original));
⚠️ 순환 참조가 있는 객체는 위 방법으로 복사하면 오류 발생할 수 있음.
03. 값의 비교 방법
03.01. 원시 값의 비교 (값 비교)
const a = 10;
const b = 10;
console.log(a === b); // true
03.02. 참조 값의 비교 (참조 주소 비교)
const obj1 = { name: "Soo" };
const obj2 = { name: "Soo" };
console.log(obj1 === obj2); // false
const obj3 = obj1;
console.log(obj1 === obj3); // true
03.03. 깊은 비교를 위한 도구 소개 (lodash.isEqual, structuredClone 등)
import isEqual from "lodash/isEqual";
const a = { x: 1, y: { z: 2 } };
const b = { x: 1, y: { z: 2 } };
console.log(isEqual(a, b)); // true
const cloned = structuredClone(a);
console.log(cloned === a); // false
console.log(isEqual(cloned, a)); // true
structuredClone은 깊은 복사 기능을 제공하지만, 함수, Symbol, DOM 요소 등 일부 복잡한 값은 복사되지 않으며 오류가 발생할 수 있습니다.
'books > deep-dive' 카테고리의 다른 글
| [Deep-Dive JavaScript] 13장 - 스코프 (0) | 2025.03.26 |
|---|---|
| [Deep-Dive JavaScript] 12장 - 함수 (0) | 2025.03.26 |
| [Deep-Dive JavaScript] 10장 - 객체 리터럴 (0) | 2025.03.25 |
| [Deep-Dive JavaScript] 6장 - 데이터 타입 (0) | 2025.03.24 |
| [Deep-Dive JavaScript] 4장 - 변수 (0) | 2025.03.23 |
- Total
- Today
- Yesterday
- JavaScript
- react
- vee-validate
- string
- pnpm 명령어
- primitive
- library mode
- pakage-lock.json
- scoped slot
- prototype
- string table
- TypeScript
- 모노레포 스크립트
- webpack
- JIT
- react-router
- bundler
- useasyncdata
- premitive
- object literal
- double-linked-list
- ViTE
- deep dive
- nuxt
- refrerence
- uselazyasyncdata
- vue
- npm ci
- 바이트 코드
- interning
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
