ECMAScript 2015에서부터 symbol은 number와 string과 같은 기본 데이타 타입(primitive data type)이 되었다.
symbol값들은 Symbol이라는 생성자를 호출하여 생성된다.
let sym1 = Symbol(); let sym2 = Symbol("key"); // string "key"는 optional입니다.
Symbol은 변경 불가능하며 고유합니다.
let sym2 = Symbol("key"); let sym3 = Symbol("key"); sym2 === sym3; // false, symbols are unique
string과 마찬가지로 symbol을 객체 프로퍼티의 키로 사용할 수 있습니다.
let sym = Symbol(); let obj = { [sym]: "value" }; console.log(obj[sym]); // "value"
Symbol을 계산된 프로퍼티 선언과 결합하여 객체 프로퍼티와 클래스 멤버를 선언할 수도 있습니다.
const getClassNameSymbol = Symbol(); class C { [getClassNameSymbol](){ return "C"; } } let c = new C(); let className = c[getClassNameSymbol](); // "C"
사용자 정의 Symbol 외에도 잘 알려진 내장 Symbol이 있습니다. 내장 Symbol는 언어 내부 동작을 나타내는 데 사용됩니다.
다음은 잘 알려진 Symbol 목록입니다.
Symbol.hasInstance
생성자 객체가 생성자의 인스턴스 중 하나로서 객체를 인식하는지 여부를 결정하는 메서드입니다. instanceof 연산자의 의미에 의해 호출됩니다.
Symbol.isConcatSpreadable
객체가 Array.prototype.concat에 의해 배열 요소로 병합되어야 함을 나타내는 부울 값입니다.
Symbol.iterator
객체의 기본 반복자를 반환하는 메서드입니다. for-of 구문의 의미에 의해 호출됩니다.
Symbol.match
문자열과 비교하는 정규 표현식 메서드입니다. String.prototype.match 메서드에 의해 호출됩니다.
Symbol.replace
일치하는 문자열의 부분 문자열을 대체하는 정규 표현식 메서드입니다. String.prototype.replace 메서드에 의해 호출됩니다.
Symbol.search
일치하는 문자열 내에서 인덱스를 반환하는 정규 표현식 메서드입니다. String.prototype.search 메서드에 의해 호출됩니다.
Symbol.species
파생된 객체를 만드는 데 사용되는 생성자 함수의 함수값을 갖는 프로퍼티입니다.
Symbol.split
일치하는 인덱스에서 문자열을 분할하는 정규 표현식 메서드입니다. String.prototype.split 메서드에 의해 호출됩니다.
Symbol.toPrimitive
객체에 대응하는 Primitive로 변환하는 메소드입니다. ToPrimitive 추상 동작에 의해 호출됩니다.
Symbol.toStringTag
객체의 기본 문자열 Tag를 만드는데 사용되는 String 값입니다. 내장 메소드 Object.prototype.toString에 의해 호출됩니다.
Symbol.unscopables
자신의 프로퍼티 명을 가지는 객체는 관련된 객체의 ‘with’ 환경 바인딩에서 제외되는 속성 이름입니다.
(Javascript에서 가져옴. Typescript확인필요)
객체 리터럴 {…}을 사용해 객체를 만든 경우, 대괄호를 사용해 심볼형 키를 만들어야 합니다.
let id = Symbol("id"); let user = { name: "John", [id]: 123 // "id": 123은 안됨 };
키가 심볼인 프로퍼티는 for..in 반복문에서 배제됩니다.
let id = Symbol("id"); let user = { name: "John", age: 30, [id]: 123 }; for (let key in user) alert(key); // name과 age만 출력되고, 심볼은 출력되지 않습니다. // 심볼로 직접 접근하면 잘 작동합니다. alert( "직접 접근한 값: " + user[id] );
Object.keys(user)에서도 키가 심볼인 프로퍼티는 배제됩니다. '심볼형 프로퍼티 숨기기(hiding symbolic property)'라 불리는 이런 원칙 덕분에 외부 스크립트나 라이브러리는 심볼형 키를 가진 프로퍼티에 접근하지 못합니다.
그런데 Object.assign은 키가 심볼인 프로퍼티를 배제하지 않고 객체 내 모든 프로퍼티를 복사합니다.
let id = Symbol("id"); let user = { [id]: 123 }; let clone = Object.assign({}, user); alert( clone[id] ); // 123
뭔가 모순이 있는 것 같아 보이지만, 이는 의도적으로 설계된 것입니다. 객체를 복사하거나 병합할 때, 대개 id 같은 심볼을 포함한 프로퍼티 전부를 사용하고 싶어 할 것이라는 생각에서 이렇게 설계되었습니다.
앞서 살펴본 것처럼, 심볼은 이름이 같더라도 모두 별개로 취급됩니다. 그런데 이름이 같은 심볼이 같은 개체를 가리키길 원하는 경우도 가끔 있습니다. 애플리케이션 곳곳에서 심볼 “id”를 이용해 특정 프로퍼티에 접근해야 한다고 가정해 봅시다.
전역 심볼 레지스트리(global symbol registry) 는 이런 경우를 위해 만들어졌습니다. 전역 심볼 레지스트리 안에 심볼을 만들고 해당 심볼에 접근하면, 이름이 같은 경우 항상 동일한 심볼을 반환해줍니다.
레지스트리 안에 있는 심볼을 읽거나, 새로운 심볼을 생성하려면 Symbol.for(key)를 사용하면 됩니다.
이 메서드를 호출하면 이름이 key인 심볼을 반환합니다. 조건에 맞는 심볼이 레지스트리 안에 없으면 새로운 심볼 Symbol(key)을 만들고 레지스트리 안에 저장합니다.
// 전역 레지스트리에서 심볼을 읽습니다.
let id = Symbol.for("id"); // 심볼이 존재하지 않으면 새로운 심볼을 만듭니다.
// 동일한 이름을 이용해 심볼을 다시 읽습니다(좀 더 멀리 떨어진 코드에서도 가능합니다).
let idAgain = Symbol.for("id");
// 두 심볼은 같습니다.
alert( id === idAgain ); // true
전역 심볼 레지스트리 안에 있는 심볼은 전역 심볼이라고 불립니다. 애플리케이션에서 광범위하게 사용해야 하는 심볼이라면 전역 심볼을 사용하세요.
전역 심볼을 찾을 때 사용되는 Symbol.for(key)에 반대되는 메서드도 있습니다. Symbol.keyFor(sym)를 사용하면 이름을 얻을 수 있습니다.
// 이름을 이용해 심볼을 찾음 let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // 심볼을 이용해 이름을 얻음 alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id
Symbol.keyFor는 전역 심볼 레지스트리를 뒤져서 해당 심볼의 이름을 얻어냅니다. 검색 범위가 전역 심볼 레지스트리이기 때문에 전역 심볼이 아닌 심볼에는 사용할 수 없습니다. 전역 심볼이 아닌 인자가 넘어오면 Symbol.keyFor는 undefined를 반환합니다.
전역 심볼이 아닌 모든 심볼은 description 프로퍼티가 있습니다. 일반 심볼에서 이름을 얻고 싶으면 description 프로퍼티를 사용하면 됩니다.
let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name"); alert( Symbol.keyFor(globalSymbol) ); // name, 전역 심볼 alert( Symbol.keyFor(localSymbol) ); // undefined, 전역 심볼이 아님 alert( localSymbol.description ); // name
'시스템 심볼(system symbol)'은 자바스크립트 내부에서 사용되는 심볼입니다. 시스템 심볼을 활용하면 객체를 미세 조정할 수 있습니다.
명세서 내의 표, 잘 알려진 심볼(well-known symbols)에서 어떤 시스템 심볼이 있는지 살펴보세요.