Menu

자바스크립트 Symbol 완벽 정리: 고유 키와 Symbol.iterator

자바스크립트의 Symbol이 무엇이고 왜 존재하는지, 그리고 Symbol.iterator 같은 well-known symbol을 활용해 내 객체를 언어 기능에 자연스럽게 연결하는 방법까지 정리했습니다.

Symbol은 절대 겹치지 않는 고유한 값이다

자바스크립트 Symbol은 string, number, boolean, null, undefined, bigint와 함께 자바스크립트의 원시 타입(primitive type) 중 하나다. 핵심 특징은 딱 하나로 요약된다. 한 번 만든 심볼은 다른 어떤 심볼과도 절대 같지 않다는 것.

index.js
Output
Click Run to see the output here.

두 Symbol을 모두 인자 없이 만들었는데도 서로 같지 않습니다. 이건 우연이 아니라 Symbol의 존재 이유 그 자체예요. Symbol은 위조할 수도, 실수로 똑같이 다시 만들 수도, 다른 누군가의 Symbol과 충돌할 수도 없습니다.

디버깅에 도움이 되도록 설명(description)을 붙일 수는 있지만, 이 값은 Symbol의 정체성에는 전혀 영향을 주지 않습니다:

index.js
Output
Click Run to see the output here.

같은 description을 넘겨도 서로 다른 symbol이 만들어집니다. description은 그저 로그를 들여다볼 사람을 위한 이름표일 뿐이죠.

Symbol이 존재하는 이유: 충돌 없는 키

자바스크립트 Symbol이 만들어진 가장 큰 이유는 바로 이겁니다. 다른 사람이 쓰는 키와 겹칠 걱정 없이 객체에 프로퍼티를 추가할 수 있게 해주거든요. 문자열 키는 하나의 평평한 네임스페이스를 공유하기 때문에, 두 라이브러리가 둘 다 obj.meta에 메타데이터를 넣겠다고 마음먹는 순간 서로 덮어써 버립니다. 반면 symbol 키는 경쟁할 일이 없습니다. 내가 만든 symbol은 세상에 나만 가지고 있으니까요.

index.js
Output
Click Run to see the output here.

[ID]: 42에서 대괄호는 계산된 속성(computed property) 문법입니다. 즉, "ID가 가진 값을 키로 사용하라"는 뜻이죠. 같은 ID 심볼을 가지고 있는 코드만 이 속성을 읽거나 덮어쓸 수 있습니다. 다른 모듈이 문자열 "id"를 쓰든, 자기 나름대로 Symbol("id")를 새로 만들든, 그건 전혀 다른 슬롯을 가리키는 셈입니다.

Symbol 키는 (거의) 숨겨진다

Symbol을 키로 쓴 속성은 for...in이나 Object.keys, JSON.stringify에 잡히지 않습니다. 완전히 비밀은 아니어서 작정하고 찾으려는 코드는 여전히 접근할 수 있지만, 평범한 순회 작업에서는 조용히 빠져 있어 방해가 되지 않습니다.

index.js
Output
Click Run to see the output here.

Object.keysJSON.stringify는 심볼 키를 아예 건너뜁니다. 심볼 프로퍼티를 의도적으로 찾고 싶다면 Object.getOwnPropertySymbolsReflect.ownKeys를 쓰면 됩니다. 바로 이 점이 메타데이터 용도로 딱 맞는 특징이에요. 찾으려고 마음먹으면 보이지만, 평범하게 문자열 키만 훑는 코드에서는 보이지 않거든요.

Symbol.for로 심볼 공유하기

Symbol()로 만든 심볼은 그 자리에서만 쓰는 고유한 값입니다. 그런데 때로는 정반대가 필요할 때가 있어요. 프로그램 어디서든, 심지어 모듈이 달라도 동일한 심볼을 공유해서 서로 다른 코드 조각이 같은 키를 참조하도록 만들고 싶을 때죠. 이럴 때 쓰는 게 바로 Symbol.for입니다.

index.js
Output
Click Run to see the output here.

Symbol.for(key)는 전역 심볼 레지스트리를 확인합니다. 해당 키로 등록된 심볼이 이미 있다면 그걸 그대로 돌려주고, 없으면 새로 만들어서 저장해 둡니다. 반대로 Symbol.keyFor는 등록된 심볼에서 원래 키를 꺼내올 때 씁니다(등록되지 않은 심볼이면 undefined를 반환합니다).

사실 대부분의 상황에서는 그냥 Symbol()만으로 충분합니다. 모듈 경계를 넘어 정말로 같은 심볼을 공유해야 할 때만 Symbol.for를 꺼내 쓰세요.

잘 알려진 심볼(Well-Known Symbols): 언어 내부로 연결되는 훅

심볼이 진짜 힘을 발휘하는 지점이 바로 여기입니다. 자바스크립트에는 언어 자체가 객체에서 찾아보는 미리 정의된 심볼들, 이른바 well-known symbols 가 있습니다. 이 키에 메서드를 구현해 두면 여러분의 객체가 자바스크립트 빌트인 기능과 곧바로 연결됩니다.

그중에서도 가장 유용한 건 Symbol.iterator입니다. 이 키로 메서드를 가진 객체는 모두 이터러블(iterable) 이 되어, for...of, 스프레드 연산자, 구조 분해 할당, Array.from에서 바로 사용할 수 있습니다.

index.js
Output
Click Run to see the output here.

range는 배열이 아니다. 특별한 키로 메서드 하나를 달아둔 평범한 객체일 뿐이다. 그런데 그 메서드가 이터레이터를 반환하기 때문에 for...of나 스프레드 문법이 배열, 문자열, Map에서 동작하는 것과 똑같이 이 객체에도 잘 먹힌다. 규약은 이렇다. Symbol.iterator만 구현해두면 자바스크립트는 이 객체를 이터러블로 취급한다.

알아두면 좋은 well-known symbols

이 외에도 언어 곳곳에 훅을 걸 수 있는 심볼들이 몇 가지 더 있다.

index.js
Output
Click Run to see the output here.
  • Symbol.iterator — 객체를 이터러블로 만들어 준다.
  • Symbol.asyncIterator — 위와 같지만 for await...of용이다.
  • Symbol.toPrimitive — 객체가 원시 타입(숫자, 문자열, 기본값)으로 변환될 때의 동작을 직접 제어한다.
  • Symbol.hasInstance — 클래스에서 instanceof가 어떻게 판단할지 커스터마이즈한다.
  • Symbol.toStringTagObject.prototype.toString.call(obj)이 반환하는 태그를 지정한다.

전부 외울 필요는 없다. 이런 게 있다는 것만 알아두자. 내가 만든 객체를 내장 타입처럼 동작시키고 싶을 때, 보통 그에 맞는 well-known symbol이 이미 준비되어 있다.

Symbol은 문자열이 아니다

많이들 걸려 넘어지는 부분인데, Symbol은 문자열로 자동 변환되지 않는다. 그냥 문자열에 이어 붙이려고 하면 에러가 나고, 문자열을 기대하는 API에 넘겨도 마찬가지다.

index.js
Output
Click Run to see the output here.

템플릿 리터럴도 똑같이 TypeError를 던집니다. 실제로 문자열이 필요하다면 String(symbol)이나 symbol.toString()을 써야 합니다. 이 불편함은 의도된 겁니다 — 고유한 식별자 값을 실수로 평범한 문자열 데이터처럼 다루지 않도록 언어 차원에서 막아주는 거죠.

Symbol은 언제 쓰고, 언제 쓰지 말아야 할까

이럴 때 자바스크립트 Symbol을 꺼내 쓰세요:

  • 내가 만들지 않은 객체에 메타데이터를 붙여야 하는데, 키 충돌이 절대 없어야 할 때.
  • 프로토콜을 설계할 때 — 즉, "내 라이브러리와 연동하려는 객체는 이 심볼 위치에 메서드를 구현해야 한다"는 규약을 만들 때.
  • JSON.stringifyfor...in에 노출되지 않는 프로퍼티가 필요할 때.
  • Symbol.iterator를 비롯한 well-known symbols를 직접 구현할 때.

반면 이럴 땐 Symbol을 쓰지 않는 게 낫습니다:

  • 그냥 객체 키 하나가 필요하고 충돌 걱정이 없을 때. 이럴 땐 Symbol 객체 키보다 문자열이 훨씬 간단하고, 로그에도 그대로 찍혀서 디버깅이 편합니다.
  • "private 필드"가 필요할 때. 심볼로 키를 잡은 프로퍼티는 진짜로 private한 게 아닙니다 — Object.getOwnPropertySymbols로 다 찾아낼 수 있거든요. 진정한 비공개가 필요하면 클래스의 #private 필드를 쓰세요.
  • JSON.stringify로 직렬화돼야 하는 데이터를 담을 때. 심볼 키는 직렬화에서 그냥 사라집니다.

대부분의 자바스크립트 코드는 Symbol(...)을 직접 써볼 일 없이도 잘 돌아갑니다. 하지만 내 객체가 for...of로 순회되도록 만들거나, 형 변환 상황에서 자연스럽게 동작하게 하고 싶은 순간이 오면, 심볼이 바로 그 내부 동작으로 들어가는 문입니다.

다음 장: 함수 선언

Symbol, 이터레이터, 제너레이터 모두 결국 함수에 크게 기대고 있습니다 — Symbol.iterator에 저장되는 메서드, 이터레이터를 반환하는 팩토리, 그리고 대부분의 보일러플레이트를 function* 형태 뒤로 감춰주는 제너레이터까지요. 다음 장은 함수입니다. 함수를 선언하는 여러 가지 방식과, 각 방식이 어떻게 다른지부터 살펴봅니다.

자주 묻는 질문

자바스크립트에서 Symbol이란 무엇인가요?

Symbol은 값이 항상 유일하다는 걸 보장하는 원시 타입입니다. Symbol() 또는 Symbol('설명')으로 만들 수 있는데, 아무리 같은 설명을 넣어도 두 번의 호출이 같은 값을 만들어내지 않습니다. 주로 다른 코드와 키가 겹치지 않는 객체 프로퍼티 키로 쓰거나, Symbol.iterator 같은 well-known symbol을 통해 언어 기능에 후킹(hook)할 때 사용합니다.

문자열 키 대신 Symbol을 써야 하는 경우는 언제인가요?

다른 코드와 프로퍼티 이름이 부딪히면 안 될 때 Symbol을 쓰는 게 좋습니다. 예를 들어 라이브러리가 객체에 메타데이터를 붙이거나, 프레임워크가 객체에 태그를 달거나, 의도적으로 공개 API에 노출하고 싶지 않은 내부 키를 정의할 때가 대표적이죠. 반대로 가독성이 중요하고 충돌 걱정이 없는 일반적인 상황이라면 그냥 문자열 키가 낫습니다.

Symbol.iterator는 어디에 쓰이나요?

Symbol.iteratorfor...of, 스프레드(spread), 구조 분해(destructuring)가 객체를 어떻게 순회할지 알려주는 well-known symbol입니다. 객체에 Symbol.iterator 키로 이터레이터를 반환하는 메서드를 정의하면 그 객체는 이터러블이 됩니다. 배열, 문자열, Map, Set이 내부적으로 반복되는 원리도 전부 이겁니다.

Symbol()과 Symbol.for()는 어떻게 다른가요?

Symbol('x')는 호출할 때마다 완전히 새로운 Symbol을 만듭니다 — 설명이 같아도 서로 다른 값이에요. 반면 Symbol.for('x')는 전역 레지스트리를 조회해서 같은 키에 대해서는 프로그램 전체에서 동일한 Symbol을 돌려줍니다. 모듈이나 realm 간에 Symbol을 공유해야 한다면 Symbol.for, 지역적으로만 유일하면 충분하다면 Symbol()을 쓰세요.

Coddy로 코딩 배우기

시작하기