Symbol
Symbol (심볼)
Symbol은 생성자가 고유함이 보장되는 symbol 기본형 — Symbol 값 또는 그냥 Symbol이라고도 함 — 을 반환하는 내장 객체입니다. Symbol은 종종 객체에 고유한 속성 키를 추가하는 데 사용되며, 다른 코드가 그 객체에 추가할 키와 충돌하지 않고, 다른 코드가 객체에 접근할 때 일반적으로 사용하는 어떤 메커니즘에서도 숨겨집니다. 이는 약한 형태의 캡슐화, 즉 약한 정보 은닉을 가능하게 합니다. 원문은 MDN의 JavaScript 내장 객체 참고 문서입니다.
본문
Symbol은 생성자가 고유함이 보장되는 symbol 기본형을 반환하는 내장 객체입니다. Symbol은 종종 객체에 다른 어떤 코드가 추가할 키와 충돌하지 않는 고유한 속성 키를 추가하는 데 사용되며, 다른 코드가 보통 객체에 접근하는 어떤 메커니즘에서도 숨겨집니다. 이는 약한 캡슐화(weak encapsulation), 즉 약한 정보 은닉(information hiding)을 가능하게 합니다.
모든 Symbol() 호출은 고유한 Symbol을 반환함이 보장됩니다. 모든 Symbol.for("key") 호출은 주어진 "key" 값에 대해 항상 같은 Symbol을 반환합니다. Symbol.for("key")가 호출되면, 주어진 키를 가진 Symbol이 전역 Symbol 레지스트리에서 찾을 수 있으면 그 Symbol이 반환되고, 그렇지 않으면 새 Symbol이 생성되어 주어진 키 아래 전역 Symbol 레지스트리에 추가된 뒤 반환됩니다.
설명 (Description)
새 기본형 Symbol을 만들려면 선택적 문자열 설명과 함께 Symbol()을 씁니다.
const sym1 = Symbol();
const sym2 = Symbol("foo");
const sym3 = Symbol("foo");
위 코드는 세 개의 새 Symbol을 만듭니다. 참고로 Symbol("foo")는 문자열 "foo"를 Symbol로 강제 변환하지 않습니다. 호출할 때마다 새 Symbol을 만듭니다: Symbol("foo") === Symbol("foo"); // false.
new 연산자를 사용한 다음 문법은 TypeError를 던집니다.
const sym = new Symbol(); // TypeError
이렇게 함으로써 작성자가 새 Symbol 값 대신 명시적인 Symbol 래퍼 객체를 만드는 것을 막습니다. 기본형 데이터 타입 주변에 명시적 래퍼 객체를 만드는 것이 일반적으로 가능하기 때문에(예: new Boolean, new String, new Number) 이는 다소 의외일 수 있습니다. 정말로 Symbol 래퍼 객체를 만들고 싶다면 Object() 함수를 사용할 수 있습니다.
const sym = Symbol("foo");
typeof sym; // "symbol"
const symObj = Object(sym);
typeof symObj; // "object"
Symbol은 참조 동일성(reference identity)을 가진 유일한 기본형 데이터 타입이므로(즉 같은 symbol을 두 번 만들 수 없음), 어떤 면에서 객체처럼 동작합니다. 예를 들어 가비지 수집(garbage collectable)이 가능하고, 따라서 WeakMap, WeakSet, WeakRef, FinalizationRegistry 객체에 저장할 수 있습니다.
전역 Symbol 레지스트리의 공유 Symbol (Shared Symbols)
Symbol() 함수를 사용한 위 문법은 프로그램 수명 내내 고유하게 유지되는 Symbol을 만듭니다. 파일 전반, 심지어 realm 전반(각자가 자체 전역 스코프를 가짐)에서 사용 가능한 Symbol을 만들려면 Symbol.for()와 Symbol.keyFor() 메서드를 사용해 전역 Symbol 레지스트리에서 Symbol을 설정하고 검색하세요.
참고: "전역 Symbol 레지스트리"는 단지 가상적인 개념일 뿐 JavaScript 엔진의 어떤 내부 데이터 구조와도 대응하지 않을 수 있습니다. 그리고 설령 그러한 레지스트리가 존재하더라도 그 내용은 for()와 keyFor() 메서드를 통해서만 JavaScript 코드에 제공됩니다.
Symbol.for(tokenString) 메서드는 문자열 키를 받아 레지스트리에서 symbol 값을 반환하고, Symbol.keyFor(symbolValue)는 symbol 값을 받아 그에 대응하는 문자열 키를 반환합니다. 각각은 서로의 역이므로 다음이 성립합니다: Symbol.keyFor(Symbol.for("tokenString")) === "tokenString"; // true.
등록된 symbol은 어디서든 임의로 만들 수 있기 때문에, 그들이 감싸는 문자열과 거의 똑같이 동작합니다. 따라서 고유함이 보장되지 않고 가비지 수집도 되지 않습니다. 그러므로 등록된 symbol은 WeakMap, WeakSet, WeakRef, FinalizationRegistry 객체에서 허용되지 않습니다.
잘 알려진 Symbols (Well-known Symbols)
Symbol 생성자의 모든 정적 속성은 그 자체가 Symbol이며, 그 값은 realm 전반에 걸쳐 상수입니다. 이를 잘 알려진 Symbol(well-known Symbols)이라 하며, 특정 내장 JavaScript 연산을 위한 "프로토콜(protocol)" 역할을 해 사용자가 언어 동작을 사용자화할 수 있게 합니다. 예를 들어 생성자 함수가 Symbol.hasInstance를 이름으로 가진 메서드를 가지면, 이 메서드는 instanceof 연산자로 그 동작을 인코딩합니다.
잘 알려진 Symbol 이전에 JavaScript는 특정 내장 연산을 구현하기 위해 일반 속성을 사용했습니다. 예를 들어 JSON.stringify 함수는 각 객체의 toJSON() 메서드를 호출하려 하고, String 함수는 객체의 toString()과 valueOf() 메서드를 호출합니다. 그러나 언어에 연산이 더 추가될수록 각 연산에 "마법 속성(magic property)"을 지정하는 것은 하위 호환성을 깨뜨리고 언어 동작을 추론하기 어렵게 만들 수 있습니다. 잘 알려진 Symbol은 사용자화를 일반 코드(보통 문자열 속성만 읽는)에서 "보이지 않게" 만듭니다.
참고: 사양은 한때 잘 알려진 symbol을 나타내기 위해 @@<symbol-name> 표기법을 사용했습니다. 예를 들어 Symbol.hasInstance는 @@hasInstance로, Array.prototype[Symbol.iterator]() 메서드는 Array.prototype[@@iterator]()로 표기했습니다. 이 표기법은 더 이상 사양에서 사용되지 않지만, 오래된 문서나 논의에서 여전히 볼 수 있습니다.
잘 알려진 symbol은 고정된 집합으로 제공되고 프로그램 수명 내내 고유하며 Array.prototype 같은 내재(intrinsic) 객체와 비슷하므로 가비지 수집 가능성의 개념이 없으며, 그래서 WeakMap, WeakSet, WeakRef, FinalizationRegistry 객체에서도 허용됩니다.
객체에서 Symbol 속성 찾기
Object.getOwnPropertySymbols() 메서드는 Symbol의 배열을 반환하며 주어진 객체에서 Symbol 속성을 찾을 수 있게 합니다. 참고로 모든 객체는 자체 Symbol 속성 없이 초기화되므로, 객체에 Symbol 속성을 설정하지 않았다면 이 배열은 비어 있을 것입니다.
생성자 및 정적 멤버 (Constructor and static members)
Symbol()— Symbol 타입의 기본형 값을 반환합니다.new와 함께 호출하면 오류를 던집니다.- 정적 속성은 모두 잘 알려진 Symbol입니다(그 설명은 객체의 메서드가 이 Symbol을 메서드 이름으로 갖는 의미를 가리킵니다).
Symbol.asyncDispose— 객체가 스코프를 벗어날 때 자원을 비동기적으로 dispose하는 메서드.await using선언에서 사용.Symbol.asyncIterator— 객체의 기본AsyncIterator를 반환하는 메서드.for await...of에서 사용.Symbol.dispose— 객체가 스코프를 벗어날 때 자원을 dispose하는 메서드.using선언에서 사용.Symbol.hasInstance— 생성자 객체가 객체를 자신의 인스턴스로 인식하는지 결정하는 메서드.instanceof에서 사용.Symbol.isConcatSpreadable— 객체가 배열 요소로 평탄화되어야 하는지 나타내는 Boolean 값.Array.prototype.concat()에서 사용.Symbol.iterator— 객체의 기본 이터레이터를 반환하는 메서드.for...of에서 사용.Symbol.match,Symbol.matchAll,Symbol.replace,Symbol.search,Symbol.split— 문자열 매칭/치환/검색/분할 관련 메서드. 각각String.prototype.match(),matchAll(),replace(),search(),split()에서 사용.Symbol.species— 파생 객체를 만드는 데 사용되는 생성자 함수.Symbol.toPrimitive— 객체를 기본형 값으로 변환하는 메서드.Symbol.toStringTag— 객체의 기본 설명에 사용되는 문자열 값.Object.prototype.toString()에서 사용.Symbol.unscopables— 자신의 및 상속된 속성 이름이 연관 객체의with환경 바인딩에서 제외되는 객체 값.
- 정적 메서드:
Symbol.for()— 주어진 키로 전역 Symbol 레지스트리에서 기존 등록 Symbol을 검색하고 찾으면 반환합니다. 그렇지 않으면 새 Symbol이 생성되어key로 등록됩니다.Symbol.keyFor()— 주어진 Symbol에 대해 전역 Symbol 레지스트리에서 공유 Symbol 키를 검색합니다.
인스턴스 속성 및 메서드 (Instance members)
Symbol.prototype.constructor— 인스턴스 객체를 만든 생성자 함수(Symbol 인스턴스의 초기값은Symbol생성자).Symbol.prototype.description— Symbol의 설명을 담은 읽기 전용 문자열.Symbol.prototype[Symbol.toStringTag]— 값"Symbol",Object.prototype.toString()에서 사용.Symbol.prototype.toString()— Symbol의 설명을 담은 문자열 반환.Symbol.prototype.valueOf()— Symbol을 반환.Symbol.prototype[Symbol.toPrimitive]()— Symbol을 반환.
예제 (Examples)
Symbol과 typeof 연산자.
typeof Symbol() === "symbol"; // true
typeof Symbol("foo") === "symbol"; // true
typeof Symbol.iterator === "symbol"; // true
Symbol 타입 변환. Symbol을 숫자로 변환하려 하면 TypeError가 발생합니다(예: +sym 또는 sym | 0). 느슨한 동등성을 사용하면 Object(sym) == sym은 true를 반환합니다. Symbol("foo") + "bar"는 TypeError를 던집니다(Symbol을 문자열로 변환할 수 없음) — 예를 들어 이는 Symbol에서 조용히 새 문자열 속성 이름을 만드는 것을 막아 줍니다. 더 "안전한" String(sym) 변환은 Symbol에 대한 Symbol.prototype.toString() 호출처럼 동작하지만, new String(sym)은 던질 것에 주의하세요.
Symbol과 for...in 순회. Symbol은 for...in 순회에서 열거되지 않습니다. 또한 Object.getOwnPropertyNames()는 Symbol 객체 속성을 반환하지 않지만, Object.getOwnPropertySymbols()를 사용해 얻을 수 있습니다.
const obj = {};
obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";
for (const i in obj) {
console.log(i); // "c" "d"
}
Symbol과 JSON.stringify(). Symbol 키 속성은 JSON.stringify()를 사용할 때 완전히 무시됩니다. JSON.stringify({ [Symbol("foo")]: "foo" }); // '{}'.
속성 키로서의 Symbol 래퍼 객체. Symbol 래퍼 객체가 속성 키로 사용되면, 이 객체는 감싼 Symbol로 강제 변환됩니다.
const sym = Symbol("foo");
const obj = { [sym]: 1 };
obj[sym]; // 1
obj[Object(sym)]; // still 1
사양 및 호환성
Symbol은 ECMAScript® 2027 Language Specification의 # sec-symbol-objects 절에 정의되어 있습니다. Baseline "Widely available"로 분류되어 2015년 9월부터 브라우저 전반에서 사용 가능했습니다. 기능의 일부 부분은 지원 수준이 각각 다를 수 있습니다.
더 알아보기
- typeof
- JavaScript data types and data structures
- core-js의 Symbol 폴리필
- ES6 In Depth: Symbols (hacks.mozilla.org, 2015)