javascript basics #7 Symbol type

Jake Seo·2020년 11월 10일
0

javascript-basics

목록 보기
7/7

javascript basics #7 Symbol type

이 포스팅은 javascript.info/symbol 을 참조하여 작성하였습니다.

스펙에 의하면, 자바스크립트 오브젝트 프로퍼티의 키는 StringSymbol 타입 중 하나로만 지정할 수 있습니다. 숫자도 안되고, boolean도 안됩니다.

여태까지 우리는 오직 String만을 사용하였습니다. 그렇다면 Symbol이 주는 장점은 무엇인지 알아봅시다.

Symbol 이란?

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은 문자열로 자동으로 변환되지 않습니다

자바스크립트의 대부분의 값들은 문자열로의 암묵적 변환을 지원합니다. 하지만 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 프로퍼티가 생략

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은 심지어 같은 이름을 갖고 있더라도 모두 다른 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.keyFor

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

자바스크립트가 내부적으로 사용하는 "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가 있습니다.

  1. 숨겨진 오브젝트 프로퍼티를 만들고 싶을 때 (for...in이나 Object.keys에도 걸리지 않습니다.)
  2. 자바스크립트 내부적으로 쓰이는 Symbol.*을 이용해 built-in behaviors를 구현하고 싶을 때 (이후에 Symbol.iterator를 이용해 iterable한 값을 만들어보고, Symbol.toPrimitive를 이용하여 object-to-primitive 변환을 해볼 것입니다.

기술적으로 Symbol은 100% 숨겨져있지 않습니다. built-in MethodObject.getOwnPropertySymbol(obj)를 이용하여 오브젝트 프로퍼티에 있는 모든 Symbol을 얻어올 수 있습니다. 또 Reflect.ownKeys(obj)Symbol로 작성된 프로퍼티까지 전부 반환합니다. 하지만 대부분의 라이브러리, built-in functions 그리고 syntax constructs는 이러한 메소드를 사용하지 않습니다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글