
타입을 이렇게 깊게 알아본 적은 처음인 것 같습니다. 가장 흥미로웠던 부분은
Symbol이란 타입이 있다는 것은 알았는데, 메서드 등 키 값을 위해서 사용된다는 점이었습니다.Symbol
Symbol은 동일한 설명을 넣어도 유니크하게 유지됩니다.
Number는 3, 4, 5 같은 숫자 값을 가지고 있고 String은 “3”, “4”, “오 육 칠” 같은 문장 값을 가지고 있는데 Symbol은 어떻게 저장되길래 해당 값이 유니크하게 유지될 수 있는지 궁금해졌습니다. 하나하나 풀어서 보자면
1) 언어 레벨 - 자바스크립트 관점
- Symbol은 원시값입니다.
- “원시값”은 유일하고 불변합니다.
- 코드에서는
symbol1 === symbol2는 “같은 심볼이냐”만 봅니다.
즉, 자바스크립트 입장에서는 Symbol은 그저 원시값이며, 유일성은 “심볼 자체의 정체성”으로 보장됩니다.
2) 엔진 내부
- 엔진은 Symbol을 어딘가에 저장해야 합니다. 이때 심볼용 힙 엔트리를둡니다.
- 여기서 “유일성”은 엔트리에 유일하게 존재하기 때문에 발생합니다. Symbol이 같은 엔트리를 가리키면 같은 Symbol, 아니면 다른 Symbol이 되는겁니다.
여기까지만 보면 유일성은 이제 이해가 어느정도 되었습니다. 힙(Heap) 안에 할당된 메모리 블록인 힙 엔트리에 저장된 주소를 기준으로 구분한다면 객체와 마찬가지로 같은 주소를 가지는게 아니라면 구분이 어려운 것은 정상입니다.
하지만 여기서 추가 의문이 그렇다면 “Symbol은 왜 객체가 아니고 원시값일까?” 입니다.
Symbol은 왜 객체가 아닐까?
원시/객체를 구분하는 방식은 “저장 방식(주소/포인터)”가 아니라 “언어적 성질/동작”으로 정해집니다.
- 을 “언어 타입” 으로 나눕니다.
number,string,boolean,bigint,symbol,undefined,null은 원시 타입, 그 외는 객체타입.
- “메모리에 어떻게 담는가(주소냐 포인터냐)는 엔진의 세부 구현일 뿐, 나누는 기준이 아닙니다.
1) 객체의 언어적 특성
“0개 이상의 프로퍼티를 가진 값”이며, 각 프로퍼티는 속성으로 동작이 결정된다고 정의됩니다.
2) 원시값의 언어적 특정
원시 타입들을 객체가 아닌 값으로 분리해 정의합니다. 원시값에는 프로퍼티가 본질적으로 존재하지 않으며, 프로퍼티 접근 시 일시적 래퍼가 개입하는 방식으로 동작이 됩니다.
즉, 단순하게 주소값을 가진다고 객체값이 아닌 것을 확인할 수 있습니다.
불변
자바스크립트에서 “불변”은 의미 입니다.
불변은 ‘값’의 성질이고, 재할당은 ‘변수’가 가리키는 대상을 바꾸는 행위다.
여기서
10을 “수정”한 적은 없습니다. 연산으로 새 값 15 가 만들어지고, 변수 x가 그 새 값을 가리키도록 바꿔 줄(재할당) 뿐입니다.불변이란 뭘까?
“불변은 값 자체의 성질”이고, “재할당은 변수 바인딩을 바꾸는 행위”입니다.
- 값은 태생적으로 바뀌지 않는다. 한 번 만들어진 값의 “내용”을 수정할 수 없다는 뜻입니다.
- 변수는 바뀐다. 변수 x가 어떤 값을 가리키는지는 바뀔 수 있습니다.
따라서
여기서 “10”의 값을 수정하는 건 애초에 불가능하고, x가 가리키는 값을 10 → 15로 바꿔(재할당) 준 겁니다.
그래서 원시값은 어디에 저장되어 있을까?
x라는 값은 태생적으로 변경되지 않는 것은 알았습니다. 그렇다면 x의 값 “10”은 어디에 저장되어 있는 걸까요? 또 + 5를 했을 때의 “15”는 어디에 저장되어 있을까요? 객체라면 “메모리”의 주소 값을 변수가 가지고 있으며, 변수는 메모리에 저장되어 있고, 객체의 값 역시 메모리에 저장되어 있다고 생각했습니다.
그래서 이부분을 조금 더 깊게 찾아본 결과를 두 가지 관점으로 분리해서 보기로 했습니다.
1. 스펙 관점
- 스펙은 “값을 어디(스택/힙)에 저장하라”를 규정하지 않습니다. 대신 변수 바인딩(x라는 이름 ↔ 어떤 값)만 정의합니다.
- 원시값은 불변이고, 객체는 가변일 수 있다는 행동만 규정합니다.
즉, “x=10; x=x+5;” 에서 스펙적으로는
- x가 가리키는 값 바인딩이 10 → 15로 교체되었을 뿐이고,
- 10이나 15가 메모리의 어디에 있느냐는 엔진 구현 문제입니다.
2. 엔진 구현 관점
엔진마다 다르지만, 대체로 다음처럼 동작합니다.
- 변수 x는 어디에? → 함수 실행 중이라면 보통 스택 프레임의 슬롯(또는 레지스터)에 x가 들어갑니다.
- 원시값은 어디에? 엔진은 값의 종류/크기에 따라 “직접 저장(즉치)” 또는 힙 객체(박싱)”을 씁니다.
- 작은 정수
- 예: 10, 15 → 보통 태그된 즉치값으로 스택 슬록/레지스터에 직접 저장됩니다.
- 그래서 x = x + 5의 결과가 작은 정수 범위라면, 15를 즉치로 x 슬롯에 덮어씁니다.
- 일반 숫자
- 표현 범위를 벗어나거나 부동소수가 필요하면 엔진이 힙 객체를 만들고, x에는 그 포인터를 저장합니다.
- 문자열
- 길이 상관없이 보통 힙에 저장(불변), 변수/필드는 포인터를 잡습니다.
- 불리언/null/undefined
- 보통 즉치값으로 표현됩니다.
- Symbol/BigInt
- 내부 데이터가 필요하므로 보통 힙 객체이고, 변수는 포인터를 가집니다.
- 객체는 어디에?
- 항상 힙에 있고, 변수/필드는 포인터를 가집니다.
- 객체의 “수정”은 같은 힙 블록의 필드값을 바꾸는 것이고, 원시값은 그 자리 값을 갈아끼우는 (재할당)방식으로 보게 됩니다.
힙을 사용하고 있는데 원시값?
일반 숫자, 문자열 등 힙 객체로 저장하고 있고, 변수가 포인터를 가진다면 객체와 다른게 뭘까?
설명에서 원시값도 변수가 포인터를 가지고 있다고 나와있는데, 이렇게 되면 객체와 크게 다를 게 없어보인다. 주소값을 가진다는 것이 객체의 특징이 아니었을까?
- 원시값은 언어 차원에서의 불변이다.
- 숫자 10 → 15로 “바꾸는” 연산은 없다.
- x = x + 5는 새 값 15를 만들어 x가 그걸 가리키게(재할당) 할 뿐이다.
레지스터/스택 측치나 힙이나 동일하게 불변입니다.
위치가 힙이라는 사실이 곧 “수정 가능”을 뜻하지는 않습니다.
객체가 가변인 이유는?
객체/배열은 프로퍼티 추가/삭제/갱신, push/pop 같은 제자리 변경 연산을 제공합니다.
원시값과 Symbol, 그리고 불변
“심볼은 원시값이다”, “원시값은 불변이다”, 객체는 가변이다”는 결과적으로 자바스크립트 언어 스펙이 정한 규칙입니다. 규칙을 엔진이 “어떻게” 저장/최적화하든, 동작의 의미는 스펙이 결정합니다.
즉, “코코넛은 코코넛이라서 코코넛이다”처럼 언어 자체가 이름표와 성격을 붙여 준 것입니다.
하지만 이렇게 정의한 이유가 있습니다.
왜 원시값을 불변으로 했을까?
자바스크립트에서 원시값은 의미적으로 불변이라면 어떤 이점이 있을까?
1. 의미 안정성
- 참조 공유 부작용 제거 :
만약 원시값이 불변이 아니라면
b가 a의 변경에 끌려가서 동일하게 15의 값을 가질 수 있다. 하지만 원시값이기 때문에 “값은 공유해도 안전하다”는 규칙이 생깁니다. 2. 예측 가능성과 디버깅 용이성
- 지식 전이 비용 감소 : “원시는 항상 새 값을 만든다”는 규칙으로, 대다수 버그(공유 참조로 인한 변경)를 차단한다.
- 시간 추적이 쉬움 : 값을 따라가며 “언제, 누가 바꿨는지” 추적할 필요가 없고, 값이 재할당된 시점만 확인하면 됩니다.