javascript basics #7 Symbol type
이 포스팅은 javascript.info/symbol 을 참조하여 작성하였습니다.
스펙에 의하면, 자바스크립트 오브젝트 프로퍼티의 키는 String
과 Symbol
타입 중 하나로만 지정할 수 있습니다. 숫자도 안되고, boolean도 안됩니다.
여태까지 우리는 오직 String
만을 사용하였습니다. 그렇다면 Symbol
이 주는 장점은 무엇인지 알아봅시다.
Symbol
은 유일무이한 식별자를 말합니다.
let id = Symbol();
위와 같은 방식으로 Symbol을 생성할 수 있습니다.
또한 Symbol을 생성할 때, Symbol에 대한 description
을 생성할 수 있는데 우리는 이것을 symbol name
이라고 부르기도 합니다.
Symbol은 위에서 설명했듯 유일무이한 식별자여서 같은 description(symbol name)을 가지고 있더라도 다른 값입니다. description은 그저 라벨정도의 역할만 하고, 어떤 것에도 영향을 끼치지 않습니다.
이를테면 아래에 같은 description을 가진 두 개의 심볼이 있지만 둘은 동일(equal)하지 않습니다.
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
symbol 이라는 키워드를 갖고 있는 루비와 같은 언어에 친숙하다면, 자바스크립트의 symbol과 헷갈리지 않도록 주의해야 합니다. 자바스크립트의 symbol은 다른 언어의 symbol과는 다릅니다.
자바스크립트의 대부분의 값들은 문자열로의 암묵적 변환을 지원합니다. 하지만 Symbol은 문자열 자동변환(auto-convert)을 지원하지 않습니다.
그래서 아래와 같이 alert
코드를 작성하면 에러가 납니다.
let id = Symbol("id");
alert(id);
이렇게 자동변환을 지원하지 않는 이유는 "language guard"가 언어를 엉망으로 사용하는 것을 막기 위해서입니다.
Symbol을 출력하고 싶을 때는 .toString()
을 직접 붙입니다.
let id = Symbol("id");
alert(id.toString());
혹은 symbol.description
을 이용해도 됩니다.
alert(id.description);
Symbol을 이용하여 숨겨진 프로퍼티를 만들 수 있습니다. 이 숨겨진 프로퍼티에는 어떠한 다른 코드도 우연히 접근하거나 값을 덮어씌울 수 없습니다.
let user = { // 다른 코드에 속함
name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // 심볼을 키로 이용하여 접근 가능
외부에서 import 해온 것과 같은 Third-party Code
를 건드린다고 가정할 때, 숨겨진 프로퍼티 기능은 매우 유용합니다.
기존 Third-part
에서 만든 오브젝트의 프로퍼티 이름 등을 고려하지 않고도 원하는 새로운 프로퍼티를 입맛대로 등록할 수 있고, 다른 곳에서 이 프로퍼티에 실수로 접근할 수 있지 않을까하는 고민도 사라집니다.
// ...
let id = Symbol("id");
user[id] = "Some Value";
let id = Symbol("id");
let user = {
name: "John",
[id]: 123
};
for..in
반복에서는 Symbol 프로퍼티가 생략됩니다.
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age without symbol
// the direct access by the symbol works
alert( "Direct: " + user[id] );
Object.keys(user)
를 하여도 Symbol 프로퍼티는 나오지 않습니다. 그래서 어떠한 스크립트나 라이브러리 루프가 오브젝트를 순환하여도 Symbol 프로퍼티는 우연히 접근될 일이 없습니다.
반면에, Object.assign
을 이용한 오브젝트 복사는 Symbol 프로퍼티까지 복사합니다.
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = Object.assign({}, user);
alert(clone[id]); // 123
이 코드는 애초에 자바스크립트에서 의도된대로 처리되는 것입니다. 일반적으로 오브젝트를 복사한다면 프로퍼티까지 전부 복사하길 원하기 때문입니다.
위에서 배운 내용에 의하면, Symbol은 심지어 같은 이름을 갖고 있더라도 모두 다른 Symbol로 취급되었습니다. 하지만 때때로 우리는 같은 심볼이 한번 더 필요할 때가 있습니다. "id"
라는 동일한 프로퍼티를 가리키는 심볼을 만들고 싶다면 어떻게 해야 할까요?
전역 Symbol은 Symbol.for
를 이용하면 가능합니다. 이런 공간을 global symbol registry 라고 합니다. 여기에 Symbol을 등록해두고 나중에 접근할 수 있습니다.
용법은 Symbol.for(key)
와 같은 형식으로 접근하면 됩니다.
// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created
// read it again (maybe from another part of the code)
let idAgain = Symbol.for("id");
// the same symbol
alert(id === idAgain); // true
이 방식은 Ruby 언어에서 사용하는 Symbol의 형식과 비슷하다.
Symbol.for(key)
는 key
에 따른 Symbol을 반환했다면,
Symbol.keyFor(symbol)
은 Symbol에 따른 key
를 반환한다.
// get symbol by name
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// get name by symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
물론 글로벌하지 않은 (non-global) symbol을 넣게 되면 올바르게 작동하지 않고 undefined
를 반환하게 됩니다.
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
alert( Symbol.keyFor(globalSymbol) ); // name, global symbol
alert( Symbol.keyFor(localSymbol) ); // undefined, not global
alert( localSymbol.description); // name
자바스크립트가 내부적으로 사용하는 "System Symbol"
이란 것도 있습니다.
우리 오브젝트의 여러가지 측면을 fine-tune
하기 위해서 "System Symbol"
을 활용할 수 있습니다.
잘 알려진 Symbol Table은 다음과 같습니다.
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
이를테면 Symbol.toPrimitive
는 우리에게 object
에서 primitive
로의 변환을 허락해줍니다.
언어와 관련된 다양한 특성들을 배우면서 다른 Symbol들과도 친숙해질 것입니다.
Symbol
은 primitive type이며, 유일한 식별자입니다.
Symbol()
을 호출하여 생성할 수 있습니다.
Symbol
은 같은 이름을 가지더라도 항상 다른 값으로 취급되며, 같은 이름을 갖는 동일한 Symbol
을 만들기 위해서는 Symbol.for(key)
를 이용해야 합니다.
Symbol
은 다음과 같은 main use case가 있습니다.
for...in
이나 Object.keys
에도 걸리지 않습니다.)Symbol.*
을 이용해 built-in behaviors
를 구현하고 싶을 때 (이후에 Symbol.iterator
를 이용해 iterable
한 값을 만들어보고, Symbol.toPrimitive
를 이용하여 object-to-primitive
변환을 해볼 것입니다.기술적으로 Symbol
은 100% 숨겨져있지 않습니다. built-in Method
인 Object.getOwnPropertySymbol(obj)
를 이용하여 오브젝트 프로퍼티에 있는 모든 Symbol
을 얻어올 수 있습니다. 또 Reflect.ownKeys(obj)
는 Symbol
로 작성된 프로퍼티까지 전부 반환합니다. 하지만 대부분의 라이브러리, built-in functions
그리고 syntax constructs
는 이러한 메소드를 사용하지 않습니다.