Index
① 심벌의 존재 목적은 무엇일까?
- ①.1 고유성 (Uniqueness)
- ①.2 충돌방지 (Collition prevention)
- ①.3 내부적인 은닉과 보호 (Encapsulation & Protection)
- ①.4 표준 인터페이스 구현 (Well-known Symbols)
② 심벌값의 공유
③ 심벌로 상수를 정의할 때의 장점
- ③.1 완벽한 고유성
- ③.2 상수의 의미 명확성
- ③.3 안전성과 신뢰성
④ 표준 객체의 프로퍼티 키와 사용자 정의 프로퍼티 키의 충돌 가능성 문제
1. 심벌의 존재 목적은 무엇일까?
자바스크립트에서 심벌(Symbol)타입이 존재하는 이유(목적)는 명확합니다.
절대적으로 충돌하지 않는 고유한 식별자(identifier)를 생성하기 위해서입니다.
1.1 고유성 (Uniqueness)
- 심벌을 생성될 때마다 항상 유일한 값을 생성합니다.
- 같은 설명(description)으로 심벌을 여러 번 생성해도 서로 완벽히 다른 고유값을 갖습니다.
const a = Symbol('id');
const b = Symbol('id');
console.log(a === b); // false (서로 다른 고유한 값)
1.2 충돌방지 (Collition prevention)
- 문자열이나 숫자는 프로퍼티 키로 쓰일 때 쉽게 중복될 수 있고, 충돌이 발생할 수 있습니다.
- 심벌을 사용하면 절대적으로 충돌하지 않는 프로퍼티 키를 만들 수 있습니다.
// 일반 문자열 키는 충돌 위험 존재
const user = {
id: 123
};
// 나중에 실수로 덮어쓰기 가능
user.id = 456;
// 심벌을 사용하면 충돌 위험이 사라짐
const userId = Symbol('id');
user[userId] = 123;
// 절대로 다른 값으로 덮어쓸 위험이 없음
1.3 내부적인 은닉과 보호 (Encapsulation & Protection)
- 심벌은 일반적인 문자열 키와 달리 외부 코드에서 쉽게 접근할 수 없습니다.
- 따라서 특정 프로퍼티를 보호하거나 은닉할 때 유용합니다.
const PASSWORD = Symbol('password');
const user = {
name: 'Alice',
[PASSWORD]: '1234'
};
console.log(user.PASSWORD); // undefined (일반적인 방법으로 접근 불가)
console.log(user[PASSWORD]); // 1234 (정확한 심벌로만 접근 가능)
1.4 표준 인터페이스 구현 (Well-known Symbols)
- 자바스크립트는 내장기능을 위해 잘 알려진 심벌(well-known symbols)을 제공합니다.
- 이를 통해 자바스크립트의 기본 동작을 재정의 할 수 있습니다.
예를 들어 Symbol.iterator
를 통해 객체가 이터러블하게 만들 수 있습니다.
const iterable = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
return { value: i++, done: i > 3 };
}
};
}
};
for (const val of iterable) {
console.log(val); // 0, 1, 2
}
🧐 Q. 심벌 값도 문자열, 숫자, 불리언과 같이 객체처럼 접근하면 암묵적으로 ‘래퍼 객체’를 생성한다. 그 의미는 무엇인가?
Answer)
“래퍼(wrapper) 객체를 생성한다” 는 의미는 자바스크립트에서 원시 값(primitive type)을 일시적으로 객체처럼 다루기 위해 임시 객체(래퍼 객체) 를 생성하는 과정을 의미합니다.
쉽게 설명하면, 자바스크립트에서는 원시 값(숫자, 문자열, 불리언, 심벌 등) 자체에는 프로퍼티나 메서드를 추가할 수 없습니다. 그렇기 때문에 원시 값에 내장된 메서드나 프로퍼티(예: .toString()
, .description
)를 호출할 때, 자바스크립트 엔진이 자동으로 임시 객체(래퍼 객체)를 생성하여 접근하도록 합니다.
const mySymbol = Symbol('mySymbol');
// 심벌 값 자체는 원시 값이므로 프로퍼티가 없음.
// 하지만 description이나 toString을 사용할 때는
// 임시 래퍼 객체(Symbol.prototype 객체)를 생성하여 접근함.
console.log(mySymbol.description); // 'mySymbol'
console.log(mySymbol.toString()); // 'Symbol(mySymbol)'
// 아래 코드와 같은 내부 동작이 발생 (사용자는 못 봄!)
console.log((Object(mySymbol)).description);
console.log((Object(mySymbol)).toString());
// mySymbol은 원시 값이므로 객체처럼 프로퍼티에 접근할 수 없습니다.
// 하지만 프로퍼티 접근 시, 자바스크립트가 원시 값을 임시적으로 객체화시켜줍니다.
// 이때 생성된 임시 객체를 래퍼(Wrapper)객체라고 부른다.
🧐 Q. “심벌이 외부에 노출되지 않는다.”의 정확한 의미는 무엇인가요?”
Answer)
심벌이 가진 실제 내부값(자바스크립트 엔진이 관리하는 고유한 값)은 절대로 외부에서 볼 수 없고 직접 접근할 수도 없습니다. 심벌은 생성 시 옵션으로 주어지는 설명(description)만 우리가 볼 수 있을 뿐, 실제 심벌의 내부 값 자체는 코드상에서 접근하거나 알 수 없다.
// Symbol로 비밀 데이터를 보호하는 예시
const SECRET_KEY = Symbol();
// 중요한 정보를 저장하는 객체
const user = {
name: 'Alice',
[SECRET_KEY]: '비밀번호123!'
};
// 객체 출력 시
console.log(user);
// { name: 'Alice', [Symbol()]: '비밀번호123!' }
// 일반적 접근 시도
console.log(user.SECRET_KEY); // ❌ undefined
console.log(user['SECRET_KEY']); // ❌ undefined
console.log(user[Symbol()]); // ❌ undefined (새로운 심벌)
// 실제 SECRET_KEY 심벌을 알지 못하면 절대 접근할 수 없음
console.log(user[SECRET_KEY]); // ✅ '비밀번호123!'
// 객체 생성시 사용했던 동익한 심벌값이 저장된 변수를 가지고 있어야만 접근 가능하다.
즉, 심벌 값 자체는 개발자의 코드 영역에서 확인 불가능한, 엔진 내부의 고유한 값으로 유지됩니다.
이러한 심벌의 특성이 바로 보완적으로 중요한 정보를 보호하거나 충돌 없는 고유 키를 만드는 데 사용되는 이유입니다.
<⭐️ 핵심포인트>
1) 심벌(Symbol)을 객체 프로퍼티의 키로 사용하는 경우, 이 프로퍼티에 접근하려면 반드시 해당 심벌 그 자체를 가지고 있어야만 접근할 수 있습니다. (= 심벌값을 갖고있는 객체명을 알아야 한다.)
- 즉, 프로퍼티 키로 사용된 심벌 값을 변수로 저장했다면, 이 변수만 있으면 언제든 접근할 수 있습니다. (=심벌값을 할당한 프로퍼티 키의 명을 알아야 한다.)
2. 심벌값의 공유
2.1 Symbo.for( )
란?
자바스크립트의 전역 심벌 레지스트리(Global Symbol Registry)라는 공간에 심벌을 저장하거나, 이미 저장된 심벌을 가져오는 메서드입니다.
- 전역에서 동일한 심벌을 공유할 수 있도록 도와줍니다.
- 키를 기준으로 심벌을 공유하며, 키가 같으면 항상 같은 신벌을 반환합니다.
const sym1 = Symbol.for('myKey');
const sym2 = Symbol.for('myKey');
console.log(sym1 === sym2); // ✅ true (전역에서 같은 심벌을 참조)
Q. 언제 사용할까?
// 라이브러리 코드
const LIB_CONFIG = Symbol.for('library.config');
// 사용자 코드
const userConfig = {
[Symbol.for('library.config')]: {
debug: true
}
};
// 어디서든 같은 심벌로 접근 가능
console.log(userConfig[Symbol.for('library.config')]); // { debug: true }
2.2 Symbo.keyFor( )
란?
Symbol.keyFor()
는 전역 심벌 레지스트리에서 심벌의 키를 찾아 반환하는 메서드입니다.
- 오직 전역 심벌 레지스트리에서만 동작합니다.
- 즉, 전역에서 생성한 심벌(Symbol.for)에만 적용됩니다.
const s1 = Symbol.for('myKey');
const s2 = Symbol('myKey');
console.log(Symbol.keyFor(s1)); // ✅ 'myKey' (전역 레지스트리에서 찾음)
console.log(Symbol.keyFor(s2)); // ❌ undefined (일반 심벌이므로 전역에 없음)
🧐 Q. Symbole의 본질은 고유성임에도 불구하고, symbol.for 메서드가 전역 심벌 레지스트리에 저장하는 이유는 무엇인가요?
Answer)
모든 Symbol이 매번 고유하다면, 다음과 같은 문제가 발생할 수 있습니다.
- 서로 다른 모듈, 서로 다른 파일, 서로 다른 라이브러리에서 같은 의미로 사용하는 Symbol을 매번 생성하면, 사실상 같은 역할의 Symbol임에도 서로 다른 Symbol이 되어 관리가 어렵습니다.
- 이렇게 되면 공통의 목적(ex: 특정라이브러리나 프레임워크의 설정)을 가진 Symbol 값을 여러 곳에서 사용할 때 일관성이 떨어집니다.
예를 들어, 두개의 다른 파일에서 다음과 같이 Symbol을 사용했다고 생각해보면
// 파일 A.js
export const KEY = Symbol('myKey');
// 파일 B.js
export const KEY = Symbol('myKey');
이때, 두 Symbol은 고유하지만 서로 다르기 때문에 동일한 역할을 수행하기 어렵습니다.
(Solution) 전역 심벌 레지스토리를 통해 얻는 이점(Symbol.for 사용)
이런 상황에서 Symbol.for를 사용하면, 같은 키로 호출된 Symbol이 하나의 전역적인 Symbol로 관리되어 공유가 가능합니다.
// 파일 A.js
const KEY = Symbol.for('sharedKey');
// 파일 B.js
const KEY = Symbol.for('sharedKey');
// A.js 와 B.js 에서 얻은 KEY 는 동일함
console.log(KEY === Symbol.for('sharedKey')); // true
이러한 방식을 사용하면 애플리케이션 전반에서 공통적으로 쓰이는 Symbol을 효율적이고 충돌 없이 관리할 수 있게 된다.
- Symbol.for를 사용하면 전역에서 Symbol을 공유할 수 있다.
- 같은 문자열 키로 접근할 경우 항상 동일한 Symbol이 반환된다.
- 이는 애플리케이션 전체적으로 의미가 공유되는 Symbol을 다룰 때 매우 유용합니다.
전역 Symbol 사용 예시
- 라이브러리의 설정이나 상태 관리
- 글로벌 상태 객체에 접근할 때의 키
- 프레임워크나 라이브러리에서 내부적으로 공유되어야 하는 특별한 값이나 설정 값들
// 라이브러리 코드
const LIB_CONFIG = Symbol.for('library.config');
// 사용자 코드
const userConfig = {
[Symbol.for('library.config')]: {
option: true
}
};
이렇게 하면 라이브러리와 사용자 코드에서 서로 동일한 Symbol로 안전하게 소통할 수 있다.
- 일반 Symbol: 항상 고유한 값 생성 (중복 절대 없음)
- 전역 Symbol(Symbol.for): 고유성 + 전역에서 공유 가능 (동일한 키 사용 시 같은 Symbol 반환)
즉, Symbol이 기본적으로 충돌 없는 고유성을 제공하지만, 때로는 “동일한 목적을 위한 Symbol을 공유할 수 있는 방법”도 필요하기 때문에 전역 심벌 레지스트리(Symbol.for) 가 존재하는 것입니다.
(1) 문자열 키로 정의한 경우
const userConfig = {
'library.config': {
option: true
}
};
// 접근 방법
console.log(userConfig['library.config']); // ✅ { option: true }
console.log(userConfig["library.config"]); // ✅ { option: true }
console.log(userConfig.library.config); // 🚨 Error 발생 (library 속성이 없음)
console.log(userConfig['library']['config']); // 🚨 Error 발생 (library 속성이 없음)
-
문자열로만 직접 접근 가능하며, 반드시 문자열 키 자체로 접근해야 합니다.
-
점(
.
) 표기법으로는 키에 특수문자(.
)가 있기 때문에 바로 접근할 수 없습니다.
(2) 심벌(Symbol) 키로 정의한 경우
const userConfig = {
[Symbol.for('library.config')]: {
option: true
}
};
// 접근 방법
console.log(userConfig[Symbol.for('library.config')]); // ✅ { option: true }
console.log(userConfig['library.config']); // ❌ undefined
console.log(userConfig.library); // ❌ undefined
- 반드시 심벌을 통해서만 접근 가능하며, 문자열 키로는 접근 불가능합니다.
- 즉, 심벌 자체를 모르면 외부에서 직접 접근할 수 없습니다. (일종의 은닉 효과)
3. 심벌로 상수를 정의할 때의 장점
3.1 완벽한 고유성
- 같은 설명(description)으로 두번 생성해도 서로 다른 값이 생성됩니다.
3.2 상수의 의미 명확성
- 심벌의 설명(description)을 통해 각 상수가 무슨 의미인지 명확히 나타낼 수 있습니다.
3.3 안전성과 신뢰성
- 프로퍼티가 외부에서 실수로 변경되거나 덮어씌어질 가능성이 현저히 줄어듭니다.
Ex. ① 숫자 상수 방식
// 숫자 상수 방식
const Direction = { UP: 1, DOWN: 2 };
// 실수로 다른 값과 충돌 가능
const Status = { SUCCESS: 1, FAIL: 0 };
console.log(Direction.UP === Status.SUCCESS); // true (충돌 발생)
Ex. ② 심벌 상수 방식
// 심벌 상수 방식
const Direction = { UP: Symbol('up'), DOWN: Symbol('down') };
// 다른 객체가 비슷한 설명을 사용해도 충돌 불가능
const Status = { SUCCESS: Symbol('up'), FAIL: Symbol('fail') };
console.log(Direction.UP === Status.SUCCESS); // false (절대 충돌하지 않음)
4. 표준 객체의 프로퍼티 키와 사용자 정의 프로퍼티 키의 충돌 가능성 문제
4.1 왜 기존 객체의 프로퍼티를 임의로 추가하면 문제가 될까?
// 사용자가 Array 프로토타입에 sum 메서드를 추가함
Array.prototype.sum = function () {
return this.reduce((acc, cur) => acc + cur, 0);
};
[1, 2].sum(); // 3
💥 문제 상황
만약 미래의 자바스크립트 표준에서 똑같은 이름(sum
)을 가진 메서드가 내장 객체에 추가된다면 어떻게 될까요?
- 표준에서 나중에 공식적으로
.sum()
메서드를 추가하면, 사용자가 추가한 메서드는 덮어 쓰여져서 충돌이 일어나거나, 예상치 못한 동작을 유발할 수 있습니다.
실제 비슷한 사례가 있었습니다.
과거 사용자들이 직접 만든 Array.prototype.find
메서드가, 나중에 ES6에서 도입된 내장 Array.prototype.find
메서드와 충돌한 사례입니다.
4.2 해결책 → 심벌(Symbol)을 사용하여 충돌 방지
// 심벌 키로 sum 메서드를 안전하게 추가
Array.prototype[Symbol.for('sum')] = function() {
return this.reduce((acc, cur) => acc + cur, 0);
};
console.log([1, 2][Symbol.for('sum')]()); // 3
- 향후 표준이 추가되더라도 충돌이나 덮어쓰기 위험이 없습니다.